From b823f4dcc779c2760e8f246c97c60d7ae0cd48b6 Mon Sep 17 00:00:00 2001 From: Failure Date: Wed, 13 Aug 2025 00:23:35 -0700 Subject: [PATCH 1/2] wip: tagger --- .../mage/client/deckeditor/CardSelector.java | 31 +- .../client/deckeditor/table/TaggerModel.java | 344 ++++++++++++++++++ .../java/mage/client/table/TablesPanel.java | 2 +- .../card/dl/sources/ScryfallApiCard.java | 2 +- .../card/dl/sources/ScryfallImageSource.java | 4 + .../java/mage/cards/repository/CardInfo.java | 2 + .../mage/cards/repository/CardRepository.java | 2 +- 7 files changed, 381 insertions(+), 6 deletions(-) create mode 100644 Mage.Client/src/main/java/mage/client/deckeditor/table/TaggerModel.java diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java index 54ce90f5586..dcc8f7e0ce6 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java @@ -97,6 +97,10 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene cardSelectorScrollPane.setOpaque(false); cardSelectorScrollPane.getViewport().setOpaque(false); + + taggerScrollPane.setOpaque(false); + taggerScrollPane.getViewport().setOpaque(false); + cbSortBy.setModel(new DefaultComboBoxModel<>(SortBy.values())); cbSortBy.setSelectedItem(sortSetting.getSortBy()); jTextFieldSearch.addActionListener(searchAction); @@ -106,11 +110,15 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene tbColor.setOpaque(true); // false = transparent tbTypes.setBackground(PreferencesDialog.getCurrentTheme().getDeckEditorToolbarBackgroundColor()); tbTypes.setOpaque(true); // false = transparent + taggerScrollPane.setBackground(PreferencesDialog.getCurrentTheme().getDeckEditorToolbarBackgroundColor()); + taggerScrollPane.setOpaque(true); cardSelectorBottomPanel.setBackground(PreferencesDialog.getCurrentTheme().getDeckEditorToolbarBackgroundColor()); cardSelectorBottomPanel.setOpaque(true); // false = transparent } private void initListViewComponents() { + taggerTable = new JTable(); + mainTable = new JTable(); mainModel = new TableModel(); @@ -136,6 +144,11 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene // mainTable.setToolTipText(cardSelectorScrollPane.getToolTipText()); cardSelectorScrollPane.setViewportView(mainTable); + + taggerScrollPane.setViewportView(taggerTable); + + taggerTable.setOpaque(false); + mainTable.setOpaque(false); cbSortBy.setEnabled(false); chkPiles.setEnabled(false); @@ -630,6 +643,9 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene jButtonClean = new javax.swing.JButton(); cardCountLabel = new javax.swing.JLabel(); cardCount = new javax.swing.JLabel(); + + taggerScrollPane = new javax.swing.JScrollPane(); + tablePanel = new javax.swing.JSplitPane(JSplitPane.HORIZONTAL_SPLIT, cardSelectorScrollPane, taggerScrollPane); tbColor.setFloatable(false); tbColor.setRollover(true); @@ -1105,6 +1121,8 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene cardSelectorBottomPanel.setOpaque(false); cardSelectorBottomPanel.setPreferredSize(new java.awt.Dimension(897, 40)); + tablePanel.setOpaque(false); + tablePanel.setPreferredSize(new java.awt.Dimension(600, 40)); jButtonAddToMain.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/deck_in.png"))); // NOI18N jButtonAddToMain.setToolTipText("Add selected cards to deck.
\nAlternative: Double click the card in card selector to move a card to the deck."); @@ -1248,8 +1266,9 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene cardCountLabel.setToolTipText("Number of cards currently shown."); cardCount.setText("0"); - + javax.swing.GroupLayout cardSelectorBottomPanelLayout = new javax.swing.GroupLayout(cardSelectorBottomPanel); + cardSelectorBottomPanel.setLayout(cardSelectorBottomPanelLayout); cardSelectorBottomPanelLayout.setHorizontalGroup( cardSelectorBottomPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -1319,9 +1338,10 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(tbColor, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(tbTypes, javax.swing.GroupLayout.DEFAULT_SIZE, 1057, Short.MAX_VALUE) - .addComponent(cardSelectorScrollPane) + .addComponent(tablePanel) .addComponent(cardSelectorBottomPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 1057, Short.MAX_VALUE) ); + layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() @@ -1329,7 +1349,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene .addGap(0, 0, 0) .addComponent(tbTypes, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, 0) - .addComponent(cardSelectorScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 237, Short.MAX_VALUE) + .addComponent(tablePanel, javax.swing.GroupLayout.DEFAULT_SIZE, 237, Short.MAX_VALUE) .addGap(0, 0, 0) .addComponent(cardSelectorBottomPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 31, javax.swing.GroupLayout.PREFERRED_SIZE)) ); @@ -1690,6 +1710,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private TableModel mainModel; private JTable mainTable; + private JTable taggerTable; private ICardGrid currentView; private final CheckBoxList listCodeSelected; @@ -1751,6 +1772,10 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private javax.swing.JToolBar tbTypes; private javax.swing.JToggleButton tbUncommon; private javax.swing.JToggleButton tbWhite; + + private javax.swing.JScrollPane taggerScrollPane; + private javax.swing.JSplitPane tablePanel; + // End of variables declaration//GEN-END:variables private final mage.client.cards.CardGrid cardGrid; // grid for piles view mode (example: selected cards in drafting) diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/table/TaggerModel.java b/Mage.Client/src/main/java/mage/client/deckeditor/table/TaggerModel.java new file mode 100644 index 00000000000..04b87f91111 --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/deckeditor/table/TaggerModel.java @@ -0,0 +1,344 @@ +package mage.client.deckeditor.table; + +import mage.client.MageFrame; +import mage.client.cards.BigCard; +import mage.client.cards.CardEventSource; +import mage.client.cards.ICardGrid; +import mage.client.deckeditor.SortSetting; +import mage.client.plugins.impl.Plugins; +import mage.client.util.ClientDefaultSettings; +import mage.client.util.ClientEventType; +import mage.client.util.Event; +import mage.client.util.Listener; +import mage.client.util.gui.GuiDisplayUtil; +import mage.constants.EnlargeMode; +import mage.cards.RateCard; +import mage.view.CardView; +import mage.view.CardsView; +import org.apache.log4j.Logger; +import org.jdesktop.swingx.JXPanel; +import org.mage.card.arcane.ManaSymbols; + +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableColumnModel; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.util.List; +import java.util.*; +import java.util.Map.Entry; + +/** + * Table Model for card list. + * + * @author nantuko + */ +public class TaggerModel extends AbstractTableModel { + + private static final long serialVersionUID = -528008802935423048L; + + private static final Logger log = Logger.getLogger(TableModel.class); + + protected final CardEventSource cardEventSource = new CardEventSource(); + protected BigCard bigCard; + protected UUID gameId; + private final Map cards = new LinkedHashMap<>(); + private final Map cardsNoCopies = new LinkedHashMap<>(); + private final List view = new ArrayList<>(); + private Dimension cardDimension; + + private boolean displayNoCopies = false; + private UpdateCountsCallback updateCountsCallback; + + private final String[] column = {"Qty", "Name", "Cost", "Color", "Type", "Stats", "Rarity", "Set", "Card number", "Draft Rating", "Color Identity"}; + public final int COLUMN_INDEX_COST = 2; + public final int COLUMN_INDEX_RATING = 9; + public final int COLUMN_INDEX_COLOR_IDENTITY = 10; + + private SortSetting sortSetting; + private int recentSortedColumn; + private boolean recentAscending; + + private boolean numberEditable; + + public TaggerModel() { + this.numberEditable = false; + } + + public void clear() { + this.clearCardEventListeners(); + this.clearCards(); + this.view.clear(); + } + + + public void clearCards() { + view.clear(); + cards.clear(); + } + + @Override + public int getRowCount() { + return view.size(); + } + + @Override + public int getColumnCount() { + return column.length; + } + + @Override + public String getColumnName(int n) { + return column[n]; + } + + @Override + public Object getValueAt(int row, int column) { + return getColumn(view.get(row), column); + } + + private Object getColumn(Object obj, int column) { + CardView c = (CardView) obj; + switch (column) { + case 0: + if (displayNoCopies) { + String key = c.getName() + c.getExpansionSetCode() + c.getCardNumber(); + Integer count = cardsNoCopies.get(key); + return count != null ? count : ""; + } + return ""; + case 1: + return c.getDisplayFullName(); // show full name in deck editor table, e.g. adventure with spell name + case 2: + // new svg images version + return ManaSymbols.getClearManaCost(c.getManaCostStr()); + /* + // old html images version + String manaCost = ""; + for (String m : c.getManaCost()) { + manaCost += m; + } + String castingCost = UI.getDisplayManaCost(manaCost); + castingCost = ManaSymbols.replaceSymbolsWithHTML(castingCost, ManaSymbols.Type.TABLE); + return "" + castingCost + ""; + return castingCost; + */ + case 3: + return c.getColorText(); + case 4: + return c.getTypeText(); + case 5: + return c.isCreature() ? c.getPower() + '/' + + c.getToughness() : "-"; + case 6: + return c.getRarity() == null ? "" : c.getRarity().toString(); + case 7: + return c.getExpansionSetCode(); + case 8: + return c.getCardNumber(); + case 9: + return RateCard.rateCard(c, Collections.emptyList()); + case 10: + return ManaSymbols.getClearManaCost(c.getOriginalColorIdentity()); + default: + return "error"; + } + } + + private void addCard(CardView card, BigCard bigCard, UUID gameId) { + if (cardDimension == null) { + cardDimension = new Dimension(ClientDefaultSettings.dimensions.getFrameWidth(), + ClientDefaultSettings.dimensions.getFrameHeight()); + } + cards.put(card.getId(), card); + + if (displayNoCopies) { + String key = card.getName() + card.getExpansionSetCode() + card.getCardNumber(); + Integer count = 1; + if (cardsNoCopies.containsKey(key)) { + count = cardsNoCopies.get(key) + 1; + } else { + view.add(card); + } + cardsNoCopies.put(key, count); + } else { + view.add(card); + } + } + + @Override + public void drawCards(SortSetting sortSetting) { + fireTableDataChanged(); + } + + public void removeCard(UUID cardId) { + cards.remove(cardId); + view.removeIf(cardView -> cardView.getId().equals(cardId)); + } + + @Override + public void addCardEventListener(Listener listener) { + cardEventSource.addListener(listener); + } + + @Override + public void clearCardEventListeners() { + cardEventSource.clearListeners(); + } + + public void setNumber(int index, int number) { + CardView card = view.get(index); + cardEventSource.fireEvent(card, ClientEventType.SET_NUMBER, number); + } + + public void doubleClick(int index, MouseEvent e, boolean forceFakeAltDown) { + CardView card = view.get(index); + cardEventSource.fireEvent(card, ClientEventType.CARD_DOUBLE_CLICK, e, forceFakeAltDown); + } + + public void removeFromMainEvent(int index) { + cardEventSource.fireEvent(ClientEventType.DECK_REMOVE_SELECTION_MAIN); + } + + public void removeFromSideEvent(int index) { + cardEventSource.fireEvent(ClientEventType.DECK_REMOVE_SELECTION_SIDEBOARD); + } + + public void addListeners(final JTable table) { + // updates card detail, listens to any key strokes + + table.addKeyListener(new KeyListener() { + @Override + public void keyPressed(KeyEvent ev) { + } + + @Override + public void keyTyped(KeyEvent ev) { + } + + @Override + public void keyReleased(KeyEvent ev) { + int row = table.getSelectedRow(); + if (row != -1) { + showImage(row); + } + } + }); + + // updates card detail, listens to any mouse clicks + table.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (!SwingUtilities.isLeftMouseButton(e)) { + return; + } + int row = table.getSelectedRow(); + if (row != -1) { + showImage(row); + } + } + }); + + // sorts + MouseListener mouse = new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (!SwingUtilities.isLeftMouseButton(e)) { + return; + } + TableColumnModel columnModel = table.getColumnModel(); + int viewColumn = columnModel.getColumnIndexAtX(e.getX()); + int column = table.convertColumnIndexToModel(viewColumn); + + if (column != -1) { + // sort ascending + boolean asc = true; + if (recentSortedColumn == column) { + asc = !recentAscending; + } + sortSetting.setSortIndex(column); + sortSetting.setAscending(asc); + sort(column, asc); + fireTableDataChanged(); + } + } + }; + table.getTableHeader().addMouseListener(mouse); + } + + private void showImage(int row) { + CardView card = view.get(row); + if (!card.getId().equals(bigCard.getCardId())) { + if (!MageFrame.isLite()) { + Image image = Plugins.instance.getOriginalImage(card); + if (image instanceof BufferedImage) { + // XXX: scaled to fit width + bigCard.setCard(card.getId(), EnlargeMode.NORMAL, image, new ArrayList<>(), false); + } else { + drawCardText(card); + } + } else { + drawCardText(card); + } + } + } + + private void drawCardText(CardView card) { + JXPanel panel = GuiDisplayUtil.getDescription(card, bigCard.getWidth(), bigCard.getHeight()); + panel.setVisible(true); + bigCard.hideTextComponent(); + bigCard.addJXPanel(card.getId(), panel); + } + + public List getCardsView() { + return view; + } + + public boolean sort(int column, boolean ascending) { + // used by addCard() to resort the cards + recentSortedColumn = column; + recentAscending = ascending; + + MageCardComparator sorter = new MageCardComparator(column, ascending); + view.sort(sorter); + + fireTableDataChanged(); + + return true; + } + + public int getRecentSortedColumn() { + return recentSortedColumn; + } + + public boolean isRecentAscending() { + return recentAscending; + } + + public void setDisplayNoCopies(boolean value) { + this.displayNoCopies = value; + } + + public void setUpdateCountsCallback(UpdateCountsCallback callback) { + this.updateCountsCallback = callback; + } + + public void setNumberEditable(boolean numberEditable) { + this.numberEditable = numberEditable; + } + + @Override + public int cardsSize() { + return cards.size(); + } + + @Override + public boolean isCellEditable(int row, int col) { + if (numberEditable && col == 0) { + return true; + } + return super.isCellEditable(row, col); + } + +} diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 0cb2e6e4242..2068250f33a 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -893,7 +893,7 @@ public class TablesPanel extends javax.swing.JPanel { formatFilterList.add(RowFilter.regexFilter("^Oathbreaker", TablesTableModel.COLUMN_DECK_TYPE)); } if (btnFormatLimited.isSelected()) { - formatFilterList.add(RowFilter.regexFilter("^Limited", TablesTableModel.COLUMN_DECK_TYPE)); + formatFilterList.add(RowFilter.regexFilter("^(?:(?:Unl)|L)imited", TablesTableModel.COLUMN_DECK_TYPE)); } if (btnFormatOther.isSelected()) { formatFilterList.add(RowFilter.regexFilter("^Momir Basic|^Constructed - Pauper|^Constructed - Frontier|^Constructed - Extended|^Constructed - Eternal|^Constructed - Historical|^Constructed - Super|^Constructed - Freeform|^Constructed - Freeform Unlimited|^Australian Highlander|^European Highlander|^Canadian Highlander|^Constructed - Old|^Constructed - Historic", TablesTableModel.COLUMN_DECK_TYPE)); diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallApiCard.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallApiCard.java index b07e82734eb..e33faeceaf2 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallApiCard.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallApiCard.java @@ -32,7 +32,7 @@ public class ScryfallApiCard { transient public String imageLarge = ""; // potentially interesting fields, can be used in other places - //public UUID oracle_id; // TODO: implement card hint with oracle/cr ruling texts (see Rulings bulk data) + public String oracle_id; // TODO: implement card hint with oracle/cr ruling texts (see Rulings bulk data) //public Integer edhrec_rank; // TODO: use it to rating cards for AI and draft bots //public Object legalities; // TODO: add verify check for bans list //public Boolean full_art; // TODO: add verify check for full art usage in sets diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java index e104610ca62..00ff1b9fd30 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSource.java @@ -6,6 +6,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; import mage.MageException; +import mage.cards.repository.CardRepository; import mage.client.remote.XmageURLConnection; import mage.client.util.CardLanguage; import mage.util.JsonUtil; @@ -485,6 +486,9 @@ public class ScryfallImageSource implements CardImageSource { continue; } + // I LOVE POTENTIAL SQL INJECTION!!!!! + CardRepository.instance.execSQL("UPDATE card SET oracleId = '"+ card.oracle_id +"' WHERE setCode = '"+ card.set +"' AND cardNumber = '"+ card.collector_number +"'"); + // keep only usefully languages // memory optimization: fewer items, from 470 MB to 96 MB diff --git a/Mage/src/main/java/mage/cards/repository/CardInfo.java b/Mage/src/main/java/mage/cards/repository/CardInfo.java index e9b8a2aab24..622df93e12a 100644 --- a/Mage/src/main/java/mage/cards/repository/CardInfo.java +++ b/Mage/src/main/java/mage/cards/repository/CardInfo.java @@ -40,6 +40,8 @@ public class CardInfo { protected String setCode; @DatabaseField(indexName = "setCode_cardNumber_index") protected String cardNumber; + @DatabaseField(indexName = "oracleId_index", canBeNull = true) + protected String oracleId; /** * Fast access to numerical card number (number without prefix/postfix: 123b -> 123) */ diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index 9f6e39d6a75..d4742bbaef6 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -38,7 +38,7 @@ public enum CardRepository { // TODO: delete db version from cards and expansions due un-used (cause dbs re-created on each update now) private static final String VERSION_ENTITY_NAME = "card"; - private static final long CARD_DB_VERSION = 54; // raise this if db structure was changed + private static final long CARD_DB_VERSION = 55; // raise this if db structure was changed private static final long CARD_CONTENT_VERSION = 241; // raise this if new cards were added to the server private Dao cardsDao; From 1473f83b150487c8db0fe0df2f3f32c018e69832 Mon Sep 17 00:00:00 2001 From: Failure Date: Fri, 15 Aug 2025 02:36:28 -0700 Subject: [PATCH 2/2] add taggers --- .../src/main/java/mage/client/MageFrame.java | 20 +- .../mage/client/deckeditor/CardSelector.java | 108 ++++- .../client/deckeditor/table/TaggerModel.java | 394 +++++++----------- .../mage/client/util/ClientEventType.java | 4 +- .../plugins/card/dl/sources/TagSource.java | 49 +++ .../mage/cards/repository/CardRepository.java | 10 + .../main/java/mage/cards/repository/Tag.java | 20 + .../mage/cards/repository/TagRelation.java | 18 + .../mage/cards/repository/TagRepository.java | 184 ++++++++ 9 files changed, 546 insertions(+), 261 deletions(-) create mode 100644 Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/TagSource.java create mode 100644 Mage/src/main/java/mage/cards/repository/Tag.java create mode 100644 Mage/src/main/java/mage/cards/repository/TagRelation.java create mode 100644 Mage/src/main/java/mage/cards/repository/TagRepository.java diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index 2c8c756e1a4..a2cfab4b9e4 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -7,6 +7,7 @@ import mage.cards.decks.Deck; import mage.cards.repository.CardRepository; import mage.cards.repository.CardScanner; import mage.cards.repository.RepositoryUtil; +import mage.cards.repository.TagRepository; import mage.client.cards.BigCard; import mage.client.chat.ChatPanelBasic; import mage.client.components.*; @@ -58,11 +59,14 @@ import org.apache.log4j.Logger; import org.junit.Assert; import org.mage.card.arcane.ManaSymbols; import org.mage.card.arcane.SvgUtils; +import org.mage.plugins.card.dl.sources.TagSource; import org.mage.plugins.card.images.DownloadPicturesService; import org.mage.plugins.card.info.CardInfoPaneImpl; import org.mage.plugins.card.utils.CardImageUtils; import org.mage.plugins.card.utils.impl.ImageManagerImpl; +import com.google.gson.Gson; + import javax.imageio.ImageIO; import javax.swing.*; import javax.swing.event.PopupMenuEvent; @@ -985,6 +989,8 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { popupDownload = new javax.swing.JPopupMenu(); menuDownloadSymbols = new javax.swing.JMenuItem(); menuDownloadImages = new javax.swing.JMenuItem(); + menuDownloadTags = new javax.swing.JMenuItem(); + desktopPane = new MageJDesktop(); mageToolbar = new javax.swing.JToolBar(); btnPreferences = new javax.swing.JButton(); @@ -1045,6 +1051,14 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { } }); popupDownload.add(menuDownloadImages); + + menuDownloadTags.setText("Download Scryfall Tagger tags (this will freeze for a bit)"); + menuDownloadTags.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + TagSource.instance.syncTagRepositiory(); + } + }); + popupDownload.add(menuDownloadTags); setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE); setMinimumSize(new java.awt.Dimension(1000, 500)); @@ -1186,7 +1200,9 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { pack(); }// //GEN-END:initComponents - private void btnDeckEditorActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnDeckEditorActionPerformed + + + private void btnDeckEditorActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnDeckEditorActionPerformed showDeckEditor(DeckEditorMode.FREE_BUILDING, null, null, null, 0); }//GEN-LAST:event_btnDeckEditorActionPerformed @@ -1635,6 +1651,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { private javax.swing.JMenuItem menuDebugTestModalDialog; private javax.swing.JMenuItem menuDownloadImages; private javax.swing.JMenuItem menuDownloadSymbols; + private javax.swing.JMenuItem menuDownloadTags; private javax.swing.JPopupMenu popupDebug; private javax.swing.JPopupMenu popupDownload; private javax.swing.JToolBar.Separator separatorDebug; @@ -1947,6 +1964,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { menuDownloadSymbols.setFont(font); menuDownloadImages.setFont(font); + menuDownloadTags.setFont(font); menuDebugTestModalDialog.setFont(font); menuDebugTestCardRenderModesDialog.setFont(font); menuDebugTestCustomCode.setFont(font); diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java index dcc8f7e0ce6..c0f1dff1ce8 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java @@ -13,6 +13,7 @@ import mage.client.constants.Constants; import mage.client.constants.Constants.SortBy; import mage.client.dialog.PreferencesDialog; import mage.client.deckeditor.table.TableModel; +import mage.client.deckeditor.table.TaggerModel; import mage.client.dialog.CheckBoxList; import mage.client.util.GUISizeHelper; import mage.client.util.gui.FastSearchUtil; @@ -35,7 +36,11 @@ import org.mage.card.arcane.ManaSymbolsCellRenderer; import javafx.util.Pair; import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableColumnModel; + import java.awt.*; import java.awt.event.*; import java.io.ByteArrayOutputStream; @@ -118,15 +123,28 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private void initListViewComponents() { taggerTable = new JTable(); - mainTable = new JTable(); - + mainModel = new TableModel(); + tagsModel = new TaggerModel(() -> filterCards()); + + tagsModel.addListeners(taggerTable); + taggerTable.setModel(tagsModel); + TableColumnModel taggerTableModel = taggerTable.getColumnModel(); + taggerTableModel.getColumn(0).setMaxWidth(30); + taggerTableModel.getColumn(0).setPreferredWidth(30); + taggerTableModel.getColumn(1).setMaxWidth(30); + taggerTableModel.getColumn(1).setPreferredWidth(30); + taggerTableModel.getColumn(2).setMaxWidth(160); + taggerTableModel.getColumn(2).setPreferredWidth(160); + taggerTableModel.getColumn(4).setMaxWidth(60); + DefaultTableCellRenderer myRenderer = (DefaultTableCellRenderer) mainTable.getDefaultRenderer(String.class); + + mainModel.addListeners(mainTable); mainTable.setModel(mainModel); mainTable.setForeground(Color.white); - DefaultTableCellRenderer myRenderer = (DefaultTableCellRenderer) mainTable.getDefaultRenderer(String.class); myRenderer.setBackground(new Color(0, 0, 0, 100)); mainTable.getColumnModel().getColumn(0).setMaxWidth(0); mainTable.getColumnModel().getColumn(0).setPreferredWidth(10); @@ -201,6 +219,9 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene mainTable.setFont(GUISizeHelper.tableFont); mainTable.setRowHeight(GUISizeHelper.tableRowHeight); + taggerTable.getTableHeader().setFont(GUISizeHelper.tableFont); + taggerTable.setFont(GUISizeHelper.tableFont); + taggerTable.setRowHeight(GUISizeHelper.tableRowHeight); } public void switchToGrid() { @@ -499,7 +520,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene if (limited) { for (Card card : cards) { - if (filter.match(card, null)) { + if (filter.match(card, null) && tagsModel.testCard(card)) { filteredCards.add(card); } } @@ -518,6 +539,10 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene if (!filter.match(card, null)) { continue; } + + if (!tagsModel.testCard(card)) { + continue; + } // found filteredCards.add(card); } @@ -644,9 +669,71 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene cardCountLabel = new javax.swing.JLabel(); cardCount = new javax.swing.JLabel(); + // Brings me back to the Tk days + taggerScrollPane = new javax.swing.JScrollPane(); - tablePanel = new javax.swing.JSplitPane(JSplitPane.HORIZONTAL_SPLIT, cardSelectorScrollPane, taggerScrollPane); + taggerContainer = new javax.swing.JPanel(); + taggerContainer.setPreferredSize(new Dimension(200, 400)); + taggerContainer.setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.fill = GridBagConstraints.BOTH; + gbc.weighty = 1; + gbc.weightx = 1; + gbc.gridx = 0; + gbc.gridy = 0; + + taggerControlBar = new javax.swing.JPanel(); + taggerControlBar.setLayout(new GridBagLayout()); + + taggerReset = new javax.swing.JButton("Reset"); + taggerReset.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + tagsModel.clear(); + } + }); + taggerContainer.add(taggerScrollPane, gbc); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weighty = 0; + gbc.weightx = 1; + gbc.gridx = 0; + gbc.gridy = 1; + taggerContainer.add(taggerControlBar, gbc); + + gbc.fill = GridBagConstraints.NONE; + gbc.weighty = 0; + gbc.weightx = 0; + gbc.gridx = 0; + gbc.gridy = 0; + + taggerControlBar.add(taggerReset, gbc); + + taggerSearch = new javax.swing.JTextField(); + taggerSearch.getDocument().addDocumentListener(new DocumentListener() { + public void removeUpdate(DocumentEvent e) { + changedUpdate(e); + } + public void insertUpdate(DocumentEvent e) { + changedUpdate(e); + } + public void changedUpdate(DocumentEvent e) { + tagsModel.search(taggerSearch.getText()); + } + }); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weighty = 1; + gbc.weightx = 1; + gbc.gridx = 1; + gbc.gridy = 0; + taggerControlBar.add(taggerSearch, gbc); + + // End hell + + tablePanel = new javax.swing.JSplitPane(JSplitPane.HORIZONTAL_SPLIT, cardSelectorScrollPane, taggerContainer); + tablePanel.setOneTouchExpandable(true); + tablePanel.setResizeWeight(1.0); + tablePanel.setDividerLocation(1.0); + tbColor.setFloatable(false); tbColor.setRollover(true); tbColor.setToolTipText("Hold the ALT-key while clicking to deselect all other colors or hold the CTRL-key to select only all other colors."); @@ -1353,6 +1440,9 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene .addGap(0, 0, 0) .addComponent(cardSelectorBottomPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 31, javax.swing.GroupLayout.PREFERRED_SIZE)) ); + + + }// //GEN-END:initComponents private void cbExpansionSetActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbExpansionSetActionPerformed @@ -1709,6 +1799,7 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene } private TableModel mainModel; + private TaggerModel tagsModel; private JTable mainTable; private JTable taggerTable; private ICardGrid currentView; @@ -1774,6 +1865,13 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene private javax.swing.JToggleButton tbWhite; private javax.swing.JScrollPane taggerScrollPane; + private javax.swing.JPanel taggerControlBar; + private javax.swing.JTextField taggerSearch; + private javax.swing.JButton taggerReset; + + + + private javax.swing.JPanel taggerContainer; private javax.swing.JSplitPane tablePanel; // End of variables declaration//GEN-END:variables diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/table/TaggerModel.java b/Mage.Client/src/main/java/mage/client/deckeditor/table/TaggerModel.java index 04b87f91111..a0b906fd673 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/table/TaggerModel.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/table/TaggerModel.java @@ -1,83 +1,68 @@ package mage.client.deckeditor.table; -import mage.client.MageFrame; import mage.client.cards.BigCard; -import mage.client.cards.CardEventSource; -import mage.client.cards.ICardGrid; -import mage.client.deckeditor.SortSetting; -import mage.client.plugins.impl.Plugins; -import mage.client.util.ClientDefaultSettings; -import mage.client.util.ClientEventType; -import mage.client.util.Event; -import mage.client.util.Listener; -import mage.client.util.gui.GuiDisplayUtil; -import mage.constants.EnlargeMode; -import mage.cards.RateCard; -import mage.view.CardView; -import mage.view.CardsView; -import org.apache.log4j.Logger; -import org.jdesktop.swingx.JXPanel; -import org.mage.card.arcane.ManaSymbols; +import mage.cards.Card; +import mage.cards.repository.CardInfo; +import mage.cards.repository.Tag; +import mage.cards.repository.TagRepository; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.log4j.Logger; import javax.swing.*; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumnModel; -import java.awt.*; import java.awt.event.*; -import java.awt.image.BufferedImage; import java.util.List; import java.util.*; -import java.util.Map.Entry; -/** - * Table Model for card list. - * - * @author nantuko - */ + public class TaggerModel extends AbstractTableModel { private static final long serialVersionUID = -528008802935423048L; private static final Logger log = Logger.getLogger(TableModel.class); - protected final CardEventSource cardEventSource = new CardEventSource(); protected BigCard bigCard; protected UUID gameId; - private final Map cards = new LinkedHashMap<>(); - private final Map cardsNoCopies = new LinkedHashMap<>(); - private final List view = new ArrayList<>(); - private Dimension cardDimension; + private List view = new ArrayList(); + public Map filtered = new HashMap(); + public Set whitelist = new HashSet(); + public Set blacklist = new HashSet(); - private boolean displayNoCopies = false; - private UpdateCountsCallback updateCountsCallback; - - private final String[] column = {"Qty", "Name", "Cost", "Color", "Type", "Stats", "Rarity", "Set", "Card number", "Draft Rating", "Color Identity"}; - public final int COLUMN_INDEX_COST = 2; - public final int COLUMN_INDEX_RATING = 9; - public final int COLUMN_INDEX_COLOR_IDENTITY = 10; - - private SortSetting sortSetting; + private final String[] column = {"Inc", "Exc", "Name", "Description", "# Cards"}; + + private boolean descending = false; + private int columnSortedBy = 0; private int recentSortedColumn; private boolean recentAscending; + private Runnable doSort; - private boolean numberEditable; - public TaggerModel() { - this.numberEditable = false; + public TaggerModel(Runnable sortFn) { + doSort = sortFn; + view = TagRepository.instance.getAllTags(); + if (view.size() == 0) { + Tag placeholder = new Tag(); + placeholder.id = ""; + placeholder.description = "Please download Scryfall tags from the 'download' tab & reopen"; + placeholder.label = "Data missing!"; + view.add(placeholder); + } } public void clear() { - this.clearCardEventListeners(); - this.clearCards(); - this.view.clear(); + view = TagRepository.instance.getAllTags(); + this.whitelist.clear(); + this.blacklist.clear(); + doSort.run(); + } + + public void search(String query) { + view = TagRepository.instance.searchTags(query); + fireTableDataChanged(); } - public void clearCards() { - view.clear(); - cards.clear(); - } - @Override public int getRowCount() { return view.size(); @@ -95,151 +80,40 @@ public class TaggerModel extends AbstractTableModel { @Override public Object getValueAt(int row, int column) { - return getColumn(view.get(row), column); + Tag tag = view.get(row); + switch (column) { + case 0: + return filtered.getOrDefault(tag.id, false) == true ? "X" : ""; + case 1: + return filtered.getOrDefault(tag.id, true) == false ? "X" : ""; + case 2: + return view.get(row).label; + case 3: + return view.get(row).description; + case 4: + return Long.toString(TagRepository.instance.getTagCardCount(view.get(row))); + } + return ""; } + - private Object getColumn(Object obj, int column) { - CardView c = (CardView) obj; - switch (column) { - case 0: - if (displayNoCopies) { - String key = c.getName() + c.getExpansionSetCode() + c.getCardNumber(); - Integer count = cardsNoCopies.get(key); - return count != null ? count : ""; - } - return ""; - case 1: - return c.getDisplayFullName(); // show full name in deck editor table, e.g. adventure with spell name - case 2: - // new svg images version - return ManaSymbols.getClearManaCost(c.getManaCostStr()); - /* - // old html images version - String manaCost = ""; - for (String m : c.getManaCost()) { - manaCost += m; - } - String castingCost = UI.getDisplayManaCost(manaCost); - castingCost = ManaSymbols.replaceSymbolsWithHTML(castingCost, ManaSymbols.Type.TABLE); - return "" + castingCost + ""; - return castingCost; - */ - case 3: - return c.getColorText(); - case 4: - return c.getTypeText(); - case 5: - return c.isCreature() ? c.getPower() + '/' - + c.getToughness() : "-"; - case 6: - return c.getRarity() == null ? "" : c.getRarity().toString(); - case 7: - return c.getExpansionSetCode(); - case 8: - return c.getCardNumber(); - case 9: - return RateCard.rateCard(c, Collections.emptyList()); - case 10: - return ManaSymbols.getClearManaCost(c.getOriginalColorIdentity()); - default: - return "error"; - } - } - - private void addCard(CardView card, BigCard bigCard, UUID gameId) { - if (cardDimension == null) { - cardDimension = new Dimension(ClientDefaultSettings.dimensions.getFrameWidth(), - ClientDefaultSettings.dimensions.getFrameHeight()); - } - cards.put(card.getId(), card); - - if (displayNoCopies) { - String key = card.getName() + card.getExpansionSetCode() + card.getCardNumber(); - Integer count = 1; - if (cardsNoCopies.containsKey(key)) { - count = cardsNoCopies.get(key) + 1; - } else { - view.add(card); - } - cardsNoCopies.put(key, count); + public void doubleClick(int index) { + Tag tag = view.get(index); + if (!filtered.containsKey(tag.id)) { + filtered.put(tag.id, true); + } else if (filtered.get(tag.id)) { + filtered.put(tag.id, false); } else { - view.add(card); + filtered.remove(tag.id); } - } - - @Override - public void drawCards(SortSetting sortSetting) { - fireTableDataChanged(); - } - - public void removeCard(UUID cardId) { - cards.remove(cardId); - view.removeIf(cardView -> cardView.getId().equals(cardId)); - } - - @Override - public void addCardEventListener(Listener listener) { - cardEventSource.addListener(listener); - } - - @Override - public void clearCardEventListeners() { - cardEventSource.clearListeners(); - } - - public void setNumber(int index, int number) { - CardView card = view.get(index); - cardEventSource.fireEvent(card, ClientEventType.SET_NUMBER, number); - } - - public void doubleClick(int index, MouseEvent e, boolean forceFakeAltDown) { - CardView card = view.get(index); - cardEventSource.fireEvent(card, ClientEventType.CARD_DOUBLE_CLICK, e, forceFakeAltDown); - } - - public void removeFromMainEvent(int index) { - cardEventSource.fireEvent(ClientEventType.DECK_REMOVE_SELECTION_MAIN); - } - - public void removeFromSideEvent(int index) { - cardEventSource.fireEvent(ClientEventType.DECK_REMOVE_SELECTION_SIDEBOARD); + fireTableCellUpdated(index, 0); + fireTableCellUpdated(index, 1); + buildBlacklist(); + buildWhitelist(); + doSort.run(); } public void addListeners(final JTable table) { - // updates card detail, listens to any key strokes - - table.addKeyListener(new KeyListener() { - @Override - public void keyPressed(KeyEvent ev) { - } - - @Override - public void keyTyped(KeyEvent ev) { - } - - @Override - public void keyReleased(KeyEvent ev) { - int row = table.getSelectedRow(); - if (row != -1) { - showImage(row); - } - } - }); - - // updates card detail, listens to any mouse clicks - table.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent e) { - if (!SwingUtilities.isLeftMouseButton(e)) { - return; - } - int row = table.getSelectedRow(); - if (row != -1) { - showImage(row); - } - } - }); - // sorts MouseListener mouse = new MouseAdapter() { @Override @@ -247,66 +121,102 @@ public class TaggerModel extends AbstractTableModel { if (!SwingUtilities.isLeftMouseButton(e)) { return; } + TableColumnModel columnModel = table.getColumnModel(); + int viewColumn = columnModel.getColumnIndexAtX(e.getX()); int column = table.convertColumnIndexToModel(viewColumn); if (column != -1) { - // sort ascending - boolean asc = true; - if (recentSortedColumn == column) { - asc = !recentAscending; - } - sortSetting.setSortIndex(column); - sortSetting.setAscending(asc); - sort(column, asc); + descending = !descending; + columnSortedBy = column; + sort(); fireTableDataChanged(); } + } }; table.getTableHeader().addMouseListener(mouse); - } - - private void showImage(int row) { - CardView card = view.get(row); - if (!card.getId().equals(bigCard.getCardId())) { - if (!MageFrame.isLite()) { - Image image = Plugins.instance.getOriginalImage(card); - if (image instanceof BufferedImage) { - // XXX: scaled to fit width - bigCard.setCard(card.getId(), EnlargeMode.NORMAL, image, new ArrayList<>(), false); - } else { - drawCardText(card); + + // updates card detail, listens to any mouse clicks + table.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + if (!SwingUtilities.isLeftMouseButton(e)) { + return; + } + if (e.getClickCount() % 2 == 0) { + doubleClick(table.getSelectedRow()); } - } else { - drawCardText(card); } - } + }); } - private void drawCardText(CardView card) { - JXPanel panel = GuiDisplayUtil.getDescription(card, bigCard.getWidth(), bigCard.getHeight()); - panel.setVisible(true); - bigCard.hideTextComponent(); - bigCard.addJXPanel(card.getId(), panel); - } - - public List getCardsView() { - return view; - } - - public boolean sort(int column, boolean ascending) { - // used by addCard() to resort the cards - recentSortedColumn = column; - recentAscending = ascending; - - MageCardComparator sorter = new MageCardComparator(column, ascending); - view.sort(sorter); + public boolean sort() { + switch (columnSortedBy) { + // I do not care. + case 0: + view.sort((t1, t2) -> (filtered.getOrDefault(t1.id, false) == true ? "X" : "").compareTo((filtered.getOrDefault(t2.id, false) == true ? "X" : ""))); + break; + case 1: + view.sort((t1, t2) -> (filtered.getOrDefault(t1.id, true) == false ? "X" : "").compareTo((filtered.getOrDefault(t2.id, true) == false ? "X" : ""))); + break; + case 2: + view.sort((t1, t2) -> ObjectUtils.compare(t1.label, t2.label)); + break; + case 3: + view.sort((t1, t2) -> ObjectUtils.compare(t1.description, t2.description)); + break; + case 4: + view.sort((t1, t2) -> TagRepository.instance.getTagCardCount(t2) - TagRepository.instance.getTagCardCount(t1)); + break; + } + + if (descending) { + Collections.reverse(view); + } fireTableDataChanged(); return true; } + + public Set buildWhitelist() { + whitelist.clear(); + for (String key : filtered.keySet()) { + if (filtered.get(key)) { + for (CardInfo card : TagRepository.instance.getCardsByTagId(key)) { + whitelist.add(card.getName()); + } + } + } + + return whitelist; + } + + public Set buildBlacklist() { + blacklist.clear(); + for (String key : filtered.keySet()) { + if (!filtered.get(key)) { + for (CardInfo card : TagRepository.instance.getCardsByTagId(key)) { + blacklist.remove(card.getName()); + } + } + } + return blacklist; + } + + public boolean testCard(Card card) { + if (!hasAnySelected()) { + return true; + } + return !blacklist.contains(card.getName()) && (whitelist.size() == 0 || whitelist.contains(card.getName())); + } + + + public boolean hasAnySelected() { + return filtered.size() > 0; + } public int getRecentSortedColumn() { return recentSortedColumn; @@ -316,29 +226,5 @@ public class TaggerModel extends AbstractTableModel { return recentAscending; } - public void setDisplayNoCopies(boolean value) { - this.displayNoCopies = value; - } - - public void setUpdateCountsCallback(UpdateCountsCallback callback) { - this.updateCountsCallback = callback; - } - - public void setNumberEditable(boolean numberEditable) { - this.numberEditable = numberEditable; - } - - @Override - public int cardsSize() { - return cards.size(); - } - - @Override - public boolean isCellEditable(int row, int col) { - if (numberEditable && col == 0) { - return true; - } - return super.isCellEditable(row, col); - } - + } diff --git a/Mage.Client/src/main/java/mage/client/util/ClientEventType.java b/Mage.Client/src/main/java/mage/client/util/ClientEventType.java index c695d94a9b0..65f4303f7ee 100644 --- a/Mage.Client/src/main/java/mage/client/util/ClientEventType.java +++ b/Mage.Client/src/main/java/mage/client/util/ClientEventType.java @@ -18,5 +18,7 @@ public enum ClientEventType { DRAFT_PICK_CARD, DRAFT_MARK_CARD, // - PLAYER_TYPE_CHANGED + PLAYER_TYPE_CHANGED, + // + TAG_DOUBLE_CLICK } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/TagSource.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/TagSource.java new file mode 100644 index 00000000000..792a0dcb1ff --- /dev/null +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/TagSource.java @@ -0,0 +1,49 @@ +package org.mage.plugins.card.dl.sources; + + +import java.util.List; + +import org.apache.log4j.Logger; + +import com.google.gson.Gson; + +import mage.cards.repository.Tag; +import mage.cards.repository.TagRepository; +import mage.client.remote.XmageURLConnection; + +class RawTag { + public String object; + public String id; + public String label; + public String type; + public String description; + public List oracle_ids; +} + +class OracleResponse { + public String object; + public boolean has_more; + public List data; +} + +public enum TagSource { + instance; + private static final Logger LOGGER = Logger.getLogger(TagSource.class); + + public void syncTagRepositiory() { + String oracle = XmageURLConnection.downloadText("https://api.scryfall.com/private/tags/oracle"); + OracleResponse response = new Gson().fromJson(oracle, OracleResponse.class); + String tagCount = Integer.toString(response.data.size()); + int i = 0; + for (RawTag rawTag : response.data) { + LOGGER.debug("Syncing tag: " + rawTag.label + " (" + Integer.toString(i) + "/" + tagCount + ")"); + Tag tag = new Tag(); + tag.id = rawTag.id; + tag.description = rawTag.description; + tag.label = rawTag.label; + TagRepository.instance.syncTag(tag, rawTag.oracle_ids); + } + + } + +} diff --git a/Mage/src/main/java/mage/cards/repository/CardRepository.java b/Mage/src/main/java/mage/cards/repository/CardRepository.java index d4742bbaef6..1cd472acf52 100644 --- a/Mage/src/main/java/mage/cards/repository/CardRepository.java +++ b/Mage/src/main/java/mage/cards/repository/CardRepository.java @@ -578,6 +578,16 @@ public enum CardRepository { return Collections.emptyList(); } + + public List findCardsWithTagRelations(List tagRelations) { + try { + List result = cardsDao.queryBuilder().where().in("oracleId", tagRelations.stream().map(id -> id.oracle_id).toArray()).query(); + return result; + } catch (SQLException e) { + e.printStackTrace(); + return new ArrayList(); + } + } public List findCards(String name, long limitByMaxAmount) { return findCards(name, limitByMaxAmount, false, true); diff --git a/Mage/src/main/java/mage/cards/repository/Tag.java b/Mage/src/main/java/mage/cards/repository/Tag.java new file mode 100644 index 00000000000..79bcbeb59eb --- /dev/null +++ b/Mage/src/main/java/mage/cards/repository/Tag.java @@ -0,0 +1,20 @@ +package mage.cards.repository; + +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.table.DatabaseTable; + +/** + * @author JayDi85 + */ +@DatabaseTable(tableName = "tag") +public class Tag { + + @DatabaseField(id = true) + public String id; + + @DatabaseField() + public String label; + + @DatabaseField(canBeNull = true) + public String description; +} diff --git a/Mage/src/main/java/mage/cards/repository/TagRelation.java b/Mage/src/main/java/mage/cards/repository/TagRelation.java new file mode 100644 index 00000000000..8a8a51301bb --- /dev/null +++ b/Mage/src/main/java/mage/cards/repository/TagRelation.java @@ -0,0 +1,18 @@ +package mage.cards.repository; + +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.table.DatabaseTable; + +/** + * @author JayDi85 + */ +@DatabaseTable(tableName = "tag_relation") +public class TagRelation { + + @DatabaseField(indexName = "tags_oracle_id_index", uniqueCombo = true) + protected String oracle_id; + + @DatabaseField(indexName = "tags_tag_id_index", foreign = true, columnName = "tag_id") + protected Tag tag; + +} diff --git a/Mage/src/main/java/mage/cards/repository/TagRepository.java b/Mage/src/main/java/mage/cards/repository/TagRepository.java new file mode 100644 index 00000000000..a80fbd2bc01 --- /dev/null +++ b/Mage/src/main/java/mage/cards/repository/TagRepository.java @@ -0,0 +1,184 @@ +package mage.cards.repository; + +import com.j256.ormlite.dao.Dao; +import com.j256.ormlite.dao.DaoManager; +import com.j256.ormlite.jdbc.JdbcConnectionSource; +import com.j256.ormlite.stmt.DeleteBuilder; +import com.j256.ormlite.stmt.QueryBuilder; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.support.DatabaseConnection; +import com.j256.ormlite.table.TableUtils; + +import mage.constants.SetType; +import org.apache.log4j.Logger; + +import java.io.File; +import java.sql.SQLException; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author North, JayDi85 + */ +public enum TagRepository { + + instance; + + private static final Logger logger = Logger.getLogger(TagRepository.class); + + // fixes limit for out of memory problems + private static final AtomicInteger databaseFixes = new AtomicInteger(); + + private static final int MAX_DATABASE_FIXES = 10; + + private static final String VERSION_ENTITY_NAME = "tags"; + private static final long TAG_VERSION = 2; // raise this if db structure was changed + private static final long TAG_RELATION_VERSION = 2; // raise this if new cards were added to the server + + private Dao tagsDao; + private Dao tagRelationDao; + + + TagRepository() { + File file = new File("db"); + if (!file.exists()) { + file.mkdirs(); + } + try { + ConnectionSource connectionSource = new JdbcConnectionSource(DatabaseUtils.prepareH2Connection(DatabaseUtils.DB_NAME_CARDS, true)); + + boolean isObsolete = RepositoryUtil.isDatabaseObsolete(connectionSource, VERSION_ENTITY_NAME, TAG_VERSION); + boolean isNewBuild = RepositoryUtil.isNewBuildRun(connectionSource, VERSION_ENTITY_NAME, TagRepository.class); // recreate db on new build + if (isObsolete || isNewBuild) { + //System.out.println("Local cards db is outdated, cleaning..."); + TableUtils.dropTable(connectionSource, TagRelation.class, true); + TableUtils.dropTable(connectionSource, Tag.class, true); + } + + TableUtils.createTableIfNotExists(connectionSource, Tag.class); + TableUtils.createTableIfNotExists(connectionSource, TagRelation.class); + + tagsDao = DaoManager.createDao(connectionSource, Tag.class); + tagRelationDao = DaoManager.createDao(connectionSource, TagRelation.class); + } catch (SQLException e) { + Logger.getLogger(TagRepository.class).error("Error creating tags repository - " + e, e); + } + } + + public List getTagsFromCard(CardInfo card) { + try { + List relations = tagRelationDao.queryForEq("oracle_id", card.oracleId); + return tagsDao.queryBuilder().where().in("id", relations.stream().map((TagRelation rel) -> rel.tag)).query(); + } catch (SQLException e) { + return new ArrayList(); + } + } + + public List getCardsByTag(Tag tag) { + try { + List relations = tagRelationDao.queryForEq("tag_id", tag.id); + return CardRepository.instance.findCardsWithTagRelations(relations); + } catch (SQLException e) { + e.printStackTrace(); + return new ArrayList(); + } + } + + public List getCardsByTagId(String id) { + try { + return getCardsByTag(tagsDao.queryForId(id)); + } catch (SQLException e) { + e.printStackTrace(); + return new ArrayList(); + } + } + + public void syncTag(final Tag tag, List oracleIds ) { + if (tag == null) { + return; + } + try { + tagsDao.createOrUpdate(tag); + tagRelationDao.callBatchTasks(() -> { + // only add new cards (no updates) + logger.info("DB: refreshing tag " + tag.label); + // clear out old ones + DeleteBuilder cleanser = tagRelationDao.deleteBuilder(); + cleanser.where().eq("tag_id", tag.id); + cleanser.delete(); + try { + for (String oracleId : oracleIds) { + TagRelation relation = new TagRelation(); + relation.oracle_id = oracleId; + relation.tag = tag; + tagRelationDao.create(relation); + } + } catch (SQLException e) { + Logger.getLogger(TagRepository.class).error("Error adding tags to DB - " + e, e); + } + return null; + }); + } catch (Exception e) { + e.printStackTrace(); + } + + try { + + } catch (Exception ex) { + // + } + } + + + + public void closeDB(boolean writeCompact) { + try { + if (tagsDao != null && tagsDao.getConnectionSource() != null) { + DatabaseConnection conn = tagsDao.getConnectionSource().getReadWriteConnection(tagsDao.getTableName()); + if (writeCompact) { + conn.executeStatement("SHUTDOWN COMPACT", DatabaseConnection.DEFAULT_RESULT_FLAGS); // compact data and rewrite whole db + } else { + conn.executeStatement("SHUTDOWN IMMEDIATELY", DatabaseConnection.DEFAULT_RESULT_FLAGS); // close without any writes + } + tagsDao.getConnectionSource().releaseConnection(conn); + } + } catch (SQLException ignore) { + } + } + + public void openDB() { + try { + ConnectionSource connectionSource = new JdbcConnectionSource(DatabaseUtils.prepareH2Connection(DatabaseUtils.DB_NAME_CARDS, true)); + tagsDao = DaoManager.createDao(connectionSource, Tag.class); + tagRelationDao = DaoManager.createDao(connectionSource, TagRelation.class); + + } catch (SQLException e) { + Logger.getLogger(TagRepository.class).error("Error opening tag repository - " + e, e); + } + } + + public List getAllTags() { + try { + return tagsDao.queryForAll(); + } catch (SQLException e) { + return new ArrayList(); + } + } + + public List searchTags(String query) { + try { + return tagsDao.queryBuilder().where().like("label", "%"+query.replace(' ', '-')+"%").query(); + } catch (SQLException e) { + return new ArrayList(); + } + } + + public int getTagCardCount(Tag tag) { + try { + return (int) tagRelationDao.queryBuilder().where().eq("tag_id", tag.id).countOf(); + } catch (SQLException e) { + return 0; + } + } + +}