From b58c4ecc155e040a4c64dd75782ed016574484dd Mon Sep 17 00:00:00 2001 From: luxick Date: Thu, 6 Jul 2017 23:04:02 +0200 Subject: [PATCH] Add sqlite for storage. --- bin/cardvault | 0 cardvault/application.py | 12 ++++-- cardvault/cardlist.py | 2 +- cardvault/database.py | 75 ++++++++++++++++++++++++++++++++++ cardvault/gui/mainwindow.glade | 41 +++++++++++++++++++ cardvault/handlers.py | 36 +++++++++++++++- cardvault/util.py | 36 +++++++++++++++- mtgsdk/card.py | 3 -- 8 files changed, 194 insertions(+), 11 deletions(-) mode change 100644 => 100755 bin/cardvault create mode 100644 cardvault/database.py 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 @@ + + + True + False + DEBUG + True + + + True + False + + + True + False + Clear Card Data + True + + + + + + True + False + Load All Cards From Gatherer + True + + + + + + True + False + Load Library to DB + True + + + + + + + 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):