diff --git a/bin/cardvault b/bin/cardvault
old mode 100644
new mode 100755
diff --git a/cardvault/application.py b/cardvault/application.py
index 0e5979a..ec82520 100644
--- a/cardvault/application.py
+++ b/cardvault/application.py
@@ -18,6 +18,7 @@ from typing import Type, Dict, List
from cardvault import handlers
from cardvault import util
+from cardvault import database
class Application:
@@ -43,6 +44,11 @@ class Application:
self.unsaved_changes = False
self.current_lib_tag = "All"
+ self.db = database.CardVaultDB(util.get_root_filename(util.DB_NAME))
+
+ # Create database tables if they do not exist
+ self.db.create_database()
+
not_found = self.ui.get_object("pageNotFound")
self.pages = {
"search": self.ui.get_object("searchView"),
@@ -314,14 +320,14 @@ class Application:
self.push_status(card.name + " added to library")
self.unsaved_changes = True
+ self.db.insert_card(card)
+
def bulk_add_card_to_lib(self, cards: list, tag: str = None):
for card in cards:
self.add_card_to_lib(card, tag)
util.log("Added {} cards to library.".format(str(len(cards))), util.LogLevel.Info)
self.push_status("Added {} cards to library.".format(str(len(cards))))
-
-
def remove_card_from_lib(self, card):
# Check if card is tagged
for card_ids in self.tags.values():
@@ -342,7 +348,7 @@ class Application:
if not mana_string:
util.log("No mana string provided", util.LogLevel.Info)
return
- icon_list = re.findall("{(.*?)}", mana_string)
+ icon_list = re.findall("{(.*?)}", mana_string.replace("/", "-"))
icon_name = "_".join(icon_list)
try:
icon = self.precon_icons[icon_name]
diff --git a/cardvault/cardlist.py b/cardvault/cardlist.py
index b0b9eec..cd30100 100644
--- a/cardvault/cardlist.py
+++ b/cardvault/cardlist.py
@@ -66,7 +66,7 @@ class CardList(Gtk.ScrolledWindow):
for card in library.values():
if card.multiverse_id is not None:
color = self.get_row_color(card, self.app.library, all_wants, self.row_colors)
- mana_cost = None if card.type == "Land" else self.app.get_mana_icons(card.mana_cost)
+ mana_cost = None if card.types.__contains__("Land") else self.app.get_mana_icons(card.mana_cost)
item = [card.multiverse_id,
card.name,
" ".join(card.supertypes if card.supertypes else ""),
diff --git a/cardvault/database.py b/cardvault/database.py
new file mode 100644
index 0000000..93f283c
--- /dev/null
+++ b/cardvault/database.py
@@ -0,0 +1,75 @@
+import sqlite3
+from mtgsdk import Card
+
+from cardvault import util
+
+
+class CardVaultDB:
+ """Data access class for sqlite3"""
+ def __init__(self, db_file: str):
+ self.db_file = db_file
+
+ def create_database(self):
+ """Create initial database"""
+ con = sqlite3.connect(self.db_file)
+
+ with con:
+ # Create library table
+ con.execute("CREATE TABLE IF NOT EXISTS `cards` "
+ "( `name` TEXT, `layout` TEXT, `manaCost` TEXT, `cmc` INTEGER, "
+ "`colors` TEXT, `names` TEXT, `type` TEXT, `supertypes` TEXT, "
+ "`subtypes` TEXT, `types` TEXT, `rarity` TEXT, `text` TEXT, "
+ "`flavor` TEXT, `artist` TEXT, `number` INTEGER, `power` TEXT, "
+ "`toughness` TEXT, `loyalty` INTEGER, `multiverseid` INTEGER UNIQUE , "
+ "`variations` TEXT, `watermark` TEXT, `border` TEXT, `timeshifted` "
+ "TEXT, `hand` TEXT, `life` TEXT, `releaseDate` TEXT, `starter` TEXT, "
+ "`printings` TEXT, `originalText` TEXT, `originalType` TEXT, "
+ "`source` TEXT, `imageUrl` TEXT, `set` TEXT, `setName` TEXT, `id` TEXT, "
+ "`legalities` TEXT, `rulings` TEXT, `foreignNames` TEXT, "
+ "PRIMARY KEY(`multiverseid`) )")
+ con.execute("CREATE TABLE IF NOT EXISTS library ( multiverse_id INT PRIMARY KEY, copies INT )")
+
+ def insert_card(self, card: Card):
+ # Connect to database
+ con = sqlite3.connect(self.db_file)
+ try:
+ with con:
+ # Map card object to database tables
+ db_values = self.card_to_table_mapping(card)
+ sql_string = "INSERT INTO `cards` VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
+ # Insert into database
+ con.execute(sql_string, db_values)
+ except sqlite3.OperationalError as err:
+ util.log("Database Error", util.LogLevel.Error)
+ util.log(str(err), util.LogLevel.Error)
+ except sqlite3.IntegrityError:
+ pass
+
+ def bulk_insert_card(self, card_list: list):
+ for card in card_list:
+ self.insert_card(card)
+
+ def clear_card_data(self):
+ con = sqlite3.connect(self.db_file)
+ try:
+ with con:
+ con.execute("DELETE FROM cards")
+ except sqlite3.OperationalError as err:
+ util.log("Database Error", util.LogLevel.Error)
+ util.log(str(err), util.LogLevel.Error)
+
+ @staticmethod
+ def card_to_table_mapping(card: Card):
+ """Return the database representation of a card object"""
+ return (str(card.name), str(card.layout), str(card.mana_cost), card.cmc, str(card.colors), str(card.names),
+ str(card.type), str(card.supertypes), str(card.subtypes), str(card.types), str(card.rarity),
+ str(card.text),
+ str(card.flavor), str(card.artist), str(card.number), str(card.power), str(card.toughness),
+ str(card.loyalty),
+ card.multiverse_id, str(card.variations), str(card.watermark), str(card.border),
+ str(card.timeshifted),
+ str(card.hand), str(card.life), str(card.release_date), str(card.starter), str(card.printings),
+ str(card.original_text),
+ str(card.original_type), str(card.source), str(card.image_url), str(card.set), str(card.set_name),
+ str(card.id),
+ str(card.legalities), str(card.rulings), str(card.foreign_names))
diff --git a/cardvault/gui/mainwindow.glade b/cardvault/gui/mainwindow.glade
index 963ad0b..2031fe3 100644
--- a/cardvault/gui/mainwindow.glade
+++ b/cardvault/gui/mainwindow.glade
@@ -155,6 +155,47 @@
+
+
+
False
diff --git a/cardvault/handlers.py b/cardvault/handlers.py
index 8c07575..d8febf4 100644
--- a/cardvault/handlers.py
+++ b/cardvault/handlers.py
@@ -1,11 +1,12 @@
import gi
gi.require_version('Gtk', '3.0')
import datetime
+import itertools
import os
from gi.repository import Gtk
-from cardvault import util
-from cardvault import application
+from cardvault import util, application
+from mtgsdk import Card
from search import SearchHandlers
from library import LibraryHandlers
@@ -91,3 +92,34 @@ class Handlers(SearchHandlers, LibraryHandlers, WantsHandlers):
return False
elif response == Gtk.ResponseType.CANCEL:
return True
+
+ # ---------------------- Debug actions -------------------------------
+
+ def do_load_lib_to_db(self, menu_item):
+ util.log("Attempt loading library to database", util.LogLevel.Info)
+ start = datetime.datetime.now()
+ self.app.db.bulk_insert_card(self.app.library.values())
+ end = datetime.datetime.now()
+ util.log("Finished in {}s".format(str(end-start)), util.LogLevel.Info)
+
+ def do_load_all_cards(self, menu_item):
+ util.log("Attempt fetching all cards from Gatherer. This may take a while...", util.LogLevel.Info)
+ start = datetime.datetime.now()
+ all_cards = []
+ for i in itertools.count():
+ new_cards = Card.where(page=i).where(pageSize=100).all()
+ if len(new_cards) == 0:
+ break
+ all_cards = all_cards + new_cards
+ util.log("Fetched page {}, {} cards so far".format(str(i), str(len(all_cards))), util.LogLevel.Info)
+ end = datetime.datetime.now()
+ util.log("Finished fetching {} cards in {}".format(str(len(all_cards)), (end - start)), util.LogLevel.Info)
+
+ util.log("Inserting cards into library...", util.LogLevel.Info)
+ self.app.db.bulk_insert_card(all_cards)
+ util.log("Done", util.LogLevel.Info)
+
+ def do_clear_card_data(self, menu_item):
+ util.log("Deleting all local card data", util.LogLevel.Info)
+ self.app.db.clear_card_data()
+ util.log("Done", util.LogLevel.Info)
\ No newline at end of file
diff --git a/cardvault/util.py b/cardvault/util.py
index 4ad916e..24bd226 100644
--- a/cardvault/util.py
+++ b/cardvault/util.py
@@ -3,6 +3,7 @@ import enum
import json
import os
import re
+import sys
from urllib import request
import gi
@@ -30,10 +31,18 @@ ICON_CACHE_PATH = os.path.expanduser('~') + "/.cardvault/icons/"
# When True Search view will list a card multiple times for each set they appear in
SHOW_FROM_ALL_SETS = True
+# First page to show after startup
START_PAGE = "search"
+# Log level of the application
+# 1 Info
+# 2 Warning
+# 3 Error
LOG_LEVEL = 1
+# Name of the database
+DB_NAME = "cardvault.db"
+
# Colors for card rows in search view
SEARCH_TREE_COLORS ={
"unowned": "black",
@@ -82,10 +91,27 @@ class LogLevel(enum.Enum):
Info = 3
+class TerminalColors:
+ HEADER = '\033[95m'
+ OKBLUE = '\033[94m'
+ OKGREEN = '\033[92m'
+ WARNING = '\033[93m'
+ FAIL = '\033[91m'
+ ENDC = '\033[0m'
+ BOLD = '\033[1m'
+ UNDERLINE = '\033[4m'
+
+
def log(message: str, log_level: LogLevel):
if log_level.value <= LOG_LEVEL:
level_string = "[" + log_level.name + "] "
- print(level_string + message)
+ if log_level.value == 2:
+ color = TerminalColors.WARNING
+ elif log_level.value == 1:
+ color = TerminalColors.BOLD + TerminalColors.FAIL
+ else:
+ color = ""
+ print(color + level_string + message+TerminalColors.ENDC)
def parse_config(filename: str, default: dict):
@@ -119,12 +145,18 @@ def save_config(config: dict, filename: str):
indent=4, separators=(',', ': ')).encode('utf-8'))
+def resource_path(relative_path):
+ if hasattr(sys, '_MEIPASS'):
+ return os.path.join(sys._MEIPASS, relative_path)
+ return os.path.join(os.path.abspath("."), relative_path)
+
+
def get_root_filename(filename: str) -> str:
return os.path.expanduser(os.path.join('~', '.cardvault', filename))
def get_ui_filename(filename: str) -> str:
- return os.path.expanduser(os.path.join(os.path.dirname(__file__), 'gui', filename))
+ return os.path.join(os.path.dirname(__file__), 'gui', filename)
def reload_image_cache(path: str) -> dict:
diff --git a/mtgsdk/card.py b/mtgsdk/card.py
index 398f085..b45f311 100644
--- a/mtgsdk/card.py
+++ b/mtgsdk/card.py
@@ -53,9 +53,6 @@ class Card(object):
self.legalities = response_dict.get('legalities')
self.rulings = response_dict.get('rulings')
self.foreign_names = response_dict.get('foreignNames')
- self.owned = None
- self.tags = []
- self.wanted = None
@staticmethod
def find(id):