Add sqlite for storage.

This commit is contained in:
luxick
2017-07-06 23:04:02 +02:00
parent 13bf42fe3e
commit b58c4ecc15
8 changed files with 194 additions and 11 deletions

0
bin/cardvault Normal file → Executable file
View File

View 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]

View File

@@ -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
View 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))

View File

@@ -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>

View File

@@ -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)

View File

@@ -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:

View File

@@ -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):