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 handlers
from cardvault import util from cardvault import util
from cardvault import database
class Application: class Application:
@@ -43,6 +44,11 @@ class Application:
self.unsaved_changes = False self.unsaved_changes = False
self.current_lib_tag = "All" 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") not_found = self.ui.get_object("pageNotFound")
self.pages = { self.pages = {
"search": self.ui.get_object("searchView"), "search": self.ui.get_object("searchView"),
@@ -314,14 +320,14 @@ class Application:
self.push_status(card.name + " added to library") self.push_status(card.name + " added to library")
self.unsaved_changes = True self.unsaved_changes = True
self.db.insert_card(card)
def bulk_add_card_to_lib(self, cards: list, tag: str = None): def bulk_add_card_to_lib(self, cards: list, tag: str = None):
for card in cards: for card in cards:
self.add_card_to_lib(card, tag) self.add_card_to_lib(card, tag)
util.log("Added {} cards to library.".format(str(len(cards))), util.LogLevel.Info) util.log("Added {} cards to library.".format(str(len(cards))), util.LogLevel.Info)
self.push_status("Added {} cards to library.".format(str(len(cards)))) self.push_status("Added {} cards to library.".format(str(len(cards))))
def remove_card_from_lib(self, card): def remove_card_from_lib(self, card):
# Check if card is tagged # Check if card is tagged
for card_ids in self.tags.values(): for card_ids in self.tags.values():
@@ -342,7 +348,7 @@ class Application:
if not mana_string: if not mana_string:
util.log("No mana string provided", util.LogLevel.Info) util.log("No mana string provided", util.LogLevel.Info)
return return
icon_list = re.findall("{(.*?)}", mana_string) icon_list = re.findall("{(.*?)}", mana_string.replace("/", "-"))
icon_name = "_".join(icon_list) icon_name = "_".join(icon_list)
try: try:
icon = self.precon_icons[icon_name] icon = self.precon_icons[icon_name]

View File

@@ -66,7 +66,7 @@ class CardList(Gtk.ScrolledWindow):
for card in library.values(): for card in library.values():
if card.multiverse_id is not None: if card.multiverse_id is not None:
color = self.get_row_color(card, self.app.library, all_wants, self.row_colors) 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, item = [card.multiverse_id,
card.name, card.name,
" ".join(card.supertypes if card.supertypes else ""), " ".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> </child>
</object> </object>
</child> </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> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>

View File

@@ -1,11 +1,12 @@
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
import datetime import datetime
import itertools
import os import os
from gi.repository import Gtk from gi.repository import Gtk
from cardvault import util from cardvault import util, application
from cardvault import application from mtgsdk import Card
from search import SearchHandlers from search import SearchHandlers
from library import LibraryHandlers from library import LibraryHandlers
@@ -91,3 +92,34 @@ class Handlers(SearchHandlers, LibraryHandlers, WantsHandlers):
return False return False
elif response == Gtk.ResponseType.CANCEL: elif response == Gtk.ResponseType.CANCEL:
return True 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 json
import os import os
import re import re
import sys
from urllib import request from urllib import request
import gi 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 # When True Search view will list a card multiple times for each set they appear in
SHOW_FROM_ALL_SETS = True SHOW_FROM_ALL_SETS = True
# First page to show after startup
START_PAGE = "search" START_PAGE = "search"
# Log level of the application
# 1 Info
# 2 Warning
# 3 Error
LOG_LEVEL = 1 LOG_LEVEL = 1
# Name of the database
DB_NAME = "cardvault.db"
# Colors for card rows in search view # Colors for card rows in search view
SEARCH_TREE_COLORS ={ SEARCH_TREE_COLORS ={
"unowned": "black", "unowned": "black",
@@ -82,10 +91,27 @@ class LogLevel(enum.Enum):
Info = 3 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): def log(message: str, log_level: LogLevel):
if log_level.value <= LOG_LEVEL: if log_level.value <= LOG_LEVEL:
level_string = "[" + log_level.name + "] " 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): def parse_config(filename: str, default: dict):
@@ -119,12 +145,18 @@ def save_config(config: dict, filename: str):
indent=4, separators=(',', ': ')).encode('utf-8')) 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: def get_root_filename(filename: str) -> str:
return os.path.expanduser(os.path.join('~', '.cardvault', filename)) return os.path.expanduser(os.path.join('~', '.cardvault', filename))
def get_ui_filename(filename: str) -> str: 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: def reload_image_cache(path: str) -> dict:

View File

@@ -53,9 +53,6 @@ class Card(object):
self.legalities = response_dict.get('legalities') self.legalities = response_dict.get('legalities')
self.rulings = response_dict.get('rulings') self.rulings = response_dict.get('rulings')
self.foreign_names = response_dict.get('foreignNames') self.foreign_names = response_dict.get('foreignNames')
self.owned = None
self.tags = []
self.wanted = None
@staticmethod @staticmethod
def find(id): def find(id):