Add sqlite for storage.
This commit is contained in:
0
bin/cardvault
Normal file → Executable file
0
bin/cardvault
Normal file → Executable file
@@ -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]
|
||||
|
||||
@@ -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 ""),
|
||||
|
||||
75
cardvault/database.py
Normal file
75
cardvault/database.py
Normal file
@@ -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))
|
||||
@@ -155,6 +155,47 @@
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="debug">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">DEBUG</property>
|
||||
<property name="use_underline">True</property>
|
||||
<child type="submenu">
|
||||
<object class="GtkMenu">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="del_card_db">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Clear Card Data</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="do_clear_card_data" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="load_all_cards">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Load All Cards From Gatherer</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="do_load_all_cards" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="lib_to_db">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Load Library to DB</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="do_load_lib_to_db" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
||||
@@ -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)
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user