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; + } + } + +}