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 54ce90f5586..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;
@@ -97,6 +102,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,19 +115,36 @@ 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();
+ 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);
@@ -136,6 +162,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);
@@ -188,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() {
@@ -486,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);
}
}
@@ -505,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);
}
@@ -630,7 +668,72 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene
jButtonClean = new javax.swing.JButton();
cardCountLabel = new javax.swing.JLabel();
cardCount = new javax.swing.JLabel();
+
+ // Brings me back to the Tk days
+
+ taggerScrollPane = new javax.swing.JScrollPane();
+ 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.");
@@ -1105,6 +1208,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 +1353,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 +1425,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,10 +1436,13 @@ 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))
);
+
+
+
}// //GEN-END:initComponents
private void cbExpansionSetActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbExpansionSetActionPerformed
@@ -1689,7 +1799,9 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene
}
private TableModel mainModel;
+ private TaggerModel tagsModel;
private JTable mainTable;
+ private JTable taggerTable;
private ICardGrid currentView;
private final CheckBoxList listCodeSelected;
@@ -1751,6 +1863,17 @@ 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.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
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..a0b906fd673
--- /dev/null
+++ b/Mage.Client/src/main/java/mage/client/deckeditor/table/TaggerModel.java
@@ -0,0 +1,230 @@
+package mage.client.deckeditor.table;
+
+import mage.client.cards.BigCard;
+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.event.*;
+import java.util.List;
+import java.util.*;
+
+
+public class TaggerModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = -528008802935423048L;
+
+ private static final Logger log = Logger.getLogger(TableModel.class);
+
+ protected BigCard bigCard;
+ protected UUID gameId;
+ private List view = new ArrayList();
+ public Map filtered = new HashMap();
+ public Set whitelist = new HashSet();
+ public Set blacklist = new HashSet();
+
+ 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;
+
+
+ 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() {
+ view = TagRepository.instance.getAllTags();
+ this.whitelist.clear();
+ this.blacklist.clear();
+ doSort.run();
+ }
+
+ public void search(String query) {
+ view = TagRepository.instance.searchTags(query);
+ fireTableDataChanged();
+ }
+
+
+ @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) {
+ 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 "";
+ }
+
+
+ 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 {
+ filtered.remove(tag.id);
+ }
+ fireTableCellUpdated(index, 0);
+ fireTableCellUpdated(index, 1);
+ buildBlacklist();
+ buildWhitelist();
+ doSort.run();
+ }
+
+ public void addListeners(final JTable table) {
+ // 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) {
+ descending = !descending;
+ columnSortedBy = column;
+ sort();
+ fireTableDataChanged();
+ }
+
+ }
+ };
+ table.getTableHeader().addMouseListener(mouse);
+
+ // 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());
+ }
+ }
+ });
+ }
+
+ 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;
+ }
+
+ public boolean isRecentAscending() {
+ return recentAscending;
+ }
+
+
+}
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/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.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/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..1cd472acf52 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;
@@ -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;
+ }
+ }
+
+}