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 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]
|
||||||
|
|||||||
@@ -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
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>
|
</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>
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user