From 1473f83b150487c8db0fe0df2f3f32c018e69832 Mon Sep 17 00:00:00 2001 From: Failure Date: Fri, 15 Aug 2025 02:36:28 -0700 Subject: [PATCH] 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; + } + } + +}