Store all data in sqlite3 database.
This commit is contained in:
@@ -14,6 +14,7 @@ import os
|
||||
import copy
|
||||
import re
|
||||
import mtgsdk
|
||||
import time
|
||||
from typing import Type, Dict, List
|
||||
|
||||
from cardvault import handlers
|
||||
@@ -69,7 +70,8 @@ class Application:
|
||||
self.library = Dict[str, Type[mtgsdk.Card]]
|
||||
self.tags = Dict[str, str]
|
||||
self.wants = Dict[str, List[Type[mtgsdk.Card]]]
|
||||
self.load_library()
|
||||
|
||||
self.load_data()
|
||||
|
||||
self.handlers = handlers.Handlers(self)
|
||||
self.ui.connect_signals(self.handlers)
|
||||
@@ -191,41 +193,26 @@ class Application:
|
||||
else:
|
||||
return value
|
||||
|
||||
def save_library(self):
|
||||
# Save library file
|
||||
util.save_file(util.get_root_filename("library"), self.library)
|
||||
# Save tags file
|
||||
util.save_file(util.get_root_filename("tags"), self.tags)
|
||||
# Save wants file
|
||||
util.save_file(util.get_root_filename("wants"), self.wants)
|
||||
def save_data(self):
|
||||
util.log("Saving Data to database", util.LogLevel.Info)
|
||||
start = time.time()
|
||||
self.db.save_library(self.library)
|
||||
self.db.save_tags(self.tags)
|
||||
self.db.save_wants(self.wants)
|
||||
end = time.time()
|
||||
util.log("Finished in {}s".format(str(round(end - start, 3))), util.LogLevel.Info)
|
||||
self.unsaved_changes = False
|
||||
self.push_status("Library saved")
|
||||
self.push_status("All data saved.")
|
||||
|
||||
def load_library(self):
|
||||
all_existing = True
|
||||
|
||||
# Load library file
|
||||
self.library = util.load_file(util.get_root_filename("library"))
|
||||
if not self.library:
|
||||
all_existing = False
|
||||
self.library = {}
|
||||
|
||||
# Load tags file
|
||||
self.tags = util.load_file(util.get_root_filename("tags"))
|
||||
if not self.tags:
|
||||
all_existing = False
|
||||
self.tags = {}
|
||||
|
||||
# Load wants lists
|
||||
self.wants = util.load_file(util.get_root_filename("wants"))
|
||||
if not self.wants:
|
||||
all_existing = False
|
||||
self.wants = {}
|
||||
|
||||
# If parts were missing save to create the files
|
||||
if not all_existing:
|
||||
self.save_library()
|
||||
self.push_status("Library loaded")
|
||||
def load_data(self):
|
||||
util.log("Loading Data from database", util.LogLevel.Info)
|
||||
start = time.time()
|
||||
self.library = self.db.get_library()
|
||||
self.tags = self.db.get_tags()
|
||||
self.wants = self.db.get_wants()
|
||||
end = time.time()
|
||||
util.log("Finished in {}s".format(str(round(end-start, 3))), util.LogLevel.Info)
|
||||
self.push_status("All data loaded.")
|
||||
|
||||
def get_untagged_cards(self):
|
||||
lib = copy.copy(self.library)
|
||||
@@ -364,6 +351,38 @@ class Application:
|
||||
else:
|
||||
return filter_text.lower() in model[iter][1].lower()
|
||||
|
||||
def load_data_legacy(self):
|
||||
all_existing = True
|
||||
# Load library file
|
||||
self.library = util.load_file(util.get_root_filename("library"))
|
||||
if not self.library:
|
||||
all_existing = False
|
||||
self.library = {}
|
||||
# Load tags file
|
||||
self.tags = util.load_file(util.get_root_filename("tags"))
|
||||
if not self.tags:
|
||||
all_existing = False
|
||||
self.tags = {}
|
||||
# Load wants lists
|
||||
self.wants = util.load_file(util.get_root_filename("wants"))
|
||||
if not self.wants:
|
||||
all_existing = False
|
||||
self.wants = {}
|
||||
# If parts were missing save to create the files
|
||||
if not all_existing:
|
||||
self.save_library_legacy()
|
||||
self.push_status("Library loaded")
|
||||
|
||||
def save_library_legacy(self):
|
||||
# Save library file
|
||||
util.save_file(util.get_root_filename("library"), self.library)
|
||||
# Save tags file
|
||||
util.save_file(util.get_root_filename("tags"), self.tags)
|
||||
# Save wants file
|
||||
util.save_file(util.get_root_filename("wants"), self.wants)
|
||||
|
||||
self.unsaved_changes = False
|
||||
self.push_status("Library saved")
|
||||
|
||||
def main():
|
||||
Application()
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import sqlite3
|
||||
import ast
|
||||
|
||||
from pygments.lexers.robotframework import _Table
|
||||
|
||||
from mtgsdk import Card
|
||||
|
||||
from cardvault import util
|
||||
@@ -29,6 +32,8 @@ class CardVaultDB:
|
||||
"`legalities` TEXT, `rulings` TEXT, `foreignNames` TEXT, "
|
||||
"PRIMARY KEY(`multiverseid`) )")
|
||||
con.execute("CREATE TABLE IF NOT EXISTS library ( multiverse_id INT PRIMARY KEY, copies INT )")
|
||||
con.execute("CREATE TABLE IF NOT EXISTS tags ( tag TEXT, multiverseid INT )")
|
||||
con.execute("CREATE TABLE IF NOT EXISTS wants ( listName TEXT, multiverseid INT )")
|
||||
|
||||
def insert_card(self, card: Card):
|
||||
# Connect to database
|
||||
@@ -59,6 +64,17 @@ class CardVaultDB:
|
||||
util.log("Database Error", util.LogLevel.Error)
|
||||
util.log(str(err), util.LogLevel.Error)
|
||||
|
||||
def clear_database(self):
|
||||
con = sqlite3.connect(self.db_file)
|
||||
try:
|
||||
with con:
|
||||
con.execute('DELETE FROM library')
|
||||
con.execute('DELETE FROM wants')
|
||||
con.execute('DELETE FROM tags')
|
||||
except sqlite3.OperationalError as err:
|
||||
util.log("Database Error", util.LogLevel.Error)
|
||||
util.log(str(err), util.LogLevel.Error)
|
||||
|
||||
def search_cards_by_name_filtered(self, term: str, filters: dict, list_size: int) -> dict:
|
||||
"""Search for cards based on the cards name with filter constrains"""
|
||||
filter_rarity = filters["rarity"]
|
||||
@@ -106,12 +122,165 @@ class CardVaultDB:
|
||||
rows = cur.fetchall()
|
||||
con.close()
|
||||
|
||||
return self.rows_to_card_dict(rows)
|
||||
|
||||
def add_card_to_lib(self, card: Card):
|
||||
"""Insert card into library"""
|
||||
con = sqlite3.connect(self.db_file)
|
||||
try:
|
||||
with con:
|
||||
con.execute("INSERT INTO `library` (`copies`, `multiverseid`) VALUES (?, ?)", (1, card.multiverse_id))
|
||||
except sqlite3.OperationalError as err:
|
||||
util.log("Database Error", util.LogLevel.Error)
|
||||
util.log(str(err), util.LogLevel.Error)
|
||||
|
||||
def get_library(self) -> dict:
|
||||
"""Load library from database"""
|
||||
con = sqlite3.connect(self.db_file)
|
||||
cur = con.cursor()
|
||||
cur.row_factory = sqlite3.Row
|
||||
cur.execute('SELECT * FROM `library` INNER JOIN `cards` ON library.multiverseid = cards.multiverseid')
|
||||
rows = cur.fetchall()
|
||||
con.close()
|
||||
|
||||
return self.rows_to_card_dict(rows)
|
||||
|
||||
def get_tags(self):
|
||||
"""Loads a dict from database with all tags and the card ids tagged"""
|
||||
con = sqlite3.connect(self.db_file)
|
||||
cur = con.cursor()
|
||||
cur.row_factory = sqlite3.Row
|
||||
|
||||
# First load all tags
|
||||
cur.execute("SELECT `tag` FROM tags GROUP BY `tag`")
|
||||
rows = cur.fetchall()
|
||||
tags = {}
|
||||
for row in rows:
|
||||
tags[row["tag"]] = []
|
||||
|
||||
# Go trough all tags an load the card ids
|
||||
for tag in tags.keys():
|
||||
cur.execute('SELECT `multiverseid` FROM `tags` WHERE tags.tag = ? AND multiverseid NOT NULL', (tag, ))
|
||||
rows = cur.fetchall()
|
||||
for row in rows:
|
||||
tags[tag].append(row["multiverseid"])
|
||||
|
||||
return tags
|
||||
|
||||
def add_tag(self, tag: str):
|
||||
"""Add a new tag to the database"""
|
||||
con = sqlite3.connect(self.db_file)
|
||||
try:
|
||||
with con:
|
||||
con.execute("INSERT INTO `tags` VALUES (?, NULL)", (tag, ))
|
||||
except sqlite3.OperationalError as err:
|
||||
util.log("Database Error", util.LogLevel.Error)
|
||||
util.log(str(err), util.LogLevel.Error)
|
||||
|
||||
def tag_card(self, tag: str, card_id: int):
|
||||
"""Add an entry for a tagged card"""
|
||||
con = sqlite3.connect(self.db_file)
|
||||
try:
|
||||
with con:
|
||||
con.execute("INSERT INTO `tags` VALUES (?, ?)", (tag, card_id))
|
||||
except sqlite3.OperationalError as err:
|
||||
util.log("Database Error", util.LogLevel.Error)
|
||||
util.log(str(err), util.LogLevel.Error)
|
||||
|
||||
def rows_to_card_dict(self, rows):
|
||||
"""Convert database rows to a card dict"""
|
||||
output = {}
|
||||
for row in rows:
|
||||
card = self.table_to_card_mapping(row)
|
||||
output[card.multiverse_id] = card
|
||||
return output
|
||||
|
||||
def add_wants_list(self, name: str):
|
||||
"""Add a new wants list to the database"""
|
||||
con = sqlite3.connect(self.db_file)
|
||||
try:
|
||||
with con:
|
||||
con.execute("INSERT INTO `wants` VALUES (?, NULL)", (name,))
|
||||
except sqlite3.OperationalError as err:
|
||||
util.log("Database Error", util.LogLevel.Error)
|
||||
util.log(str(err), util.LogLevel.Error)
|
||||
|
||||
def add_card_to_wants(self, list_name: str, card_id: int):
|
||||
"""Add a card entry to a wants list"""
|
||||
con = sqlite3.connect(self.db_file)
|
||||
try:
|
||||
with con:
|
||||
con.execute("INSERT INTO `wants` VALUES (?, ?)", (list_name, card_id))
|
||||
except sqlite3.OperationalError as err:
|
||||
util.log("Database Error", util.LogLevel.Error)
|
||||
util.log(str(err), util.LogLevel.Error)
|
||||
|
||||
def get_wants(self):
|
||||
"""Load all wants lists from database"""
|
||||
con = sqlite3.connect(self.db_file)
|
||||
cur = con.cursor()
|
||||
cur.row_factory = sqlite3.Row
|
||||
|
||||
# First load all lists
|
||||
cur.execute("SELECT `listName` FROM wants GROUP BY `listName`")
|
||||
rows = cur.fetchall()
|
||||
wants = {}
|
||||
for row in rows:
|
||||
wants[row["listName"]] = []
|
||||
|
||||
# Go trough all tags an load the card ids
|
||||
for list_name in wants.keys():
|
||||
cur.execute('SELECT * FROM '
|
||||
'(SELECT `multiverseid` FROM `wants` WHERE `listName` = ? AND multiverseid NOT NULL) tagged '
|
||||
'INNER JOIN `cards` ON tagged.multiverseid = cards.multiverseid', (list_name, ))
|
||||
rows = cur.fetchall()
|
||||
for row in rows:
|
||||
wants[list_name].append(self.table_to_card_mapping(row))
|
||||
|
||||
return wants
|
||||
|
||||
def save_library(self, cards: dict):
|
||||
"""Updates the library, adds new cards"""
|
||||
con = sqlite3.connect(self.db_file)
|
||||
try:
|
||||
with con:
|
||||
for multiverse_id in cards.keys():
|
||||
con.execute('UPDATE `library` SET `copies`=?, `multiverseid`=? WHERE multiverseid = ?;', (1, multiverse_id, multiverse_id))
|
||||
con.execute('INSERT INTO `library` (`copies`, `multiverseid`) '
|
||||
'SELECT ?,? WHERE (Select Changes() = 0);', (1, multiverse_id))
|
||||
except sqlite3.OperationalError as err:
|
||||
util.log("Database Error", util.LogLevel.Error)
|
||||
util.log(str(err), util.LogLevel.Error)
|
||||
|
||||
def save_tags(self, tags: dict):
|
||||
"""Updates the tags table"""
|
||||
con = sqlite3.connect(self.db_file)
|
||||
try:
|
||||
with con:
|
||||
for tag, id_list in tags.items():
|
||||
for id in id_list:
|
||||
con.execute('UPDATE `tags` SET `tag`=?, `multiverseid`=? WHERE tags.multiverseid = ?;', (tag, id, id))
|
||||
con.execute('INSERT INTO `tags` (`tag`, `multiverseid`) '
|
||||
'SELECT ?,? WHERE (Select Changes() = 0);', (tag, id))
|
||||
except sqlite3.OperationalError as err:
|
||||
util.log("Database Error", util.LogLevel.Error)
|
||||
util.log(str(err), util.LogLevel.Error)
|
||||
|
||||
def save_wants(self, wants: dict):
|
||||
"""Updates the wants table"""
|
||||
con = sqlite3.connect(self.db_file)
|
||||
try:
|
||||
with con:
|
||||
for name, cards in wants.items():
|
||||
for card in cards:
|
||||
con.execute('UPDATE `wants` SET `listName`=?, `multiverseid`=? WHERE wants.multiverseid = ?;',
|
||||
(name, card.multiverse_id, card.multiverse_id))
|
||||
con.execute('INSERT INTO `wants` (`listName`, `multiverseid`) '
|
||||
'SELECT ?,? WHERE (Select Changes() = 0);', (name, card.multiverse_id))
|
||||
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"""
|
||||
|
||||
@@ -166,7 +166,26 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="del_card_db">
|
||||
<object class="GtkMenuItem">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Clear DB Data</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_data">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Clear Library Data</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="do_clear_data" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="del_cards">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Clear Card Data</property>
|
||||
@@ -174,6 +193,19 @@
|
||||
<signal name="activate" handler="do_clear_card_data" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="load_data_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_data_to_db" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="load_all_cards">
|
||||
<property name="visible">True</property>
|
||||
@@ -183,15 +215,6 @@
|
||||
<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>
|
||||
|
||||
@@ -26,7 +26,7 @@ class Handlers(SearchHandlers, LibraryHandlers, WantsHandlers):
|
||||
# --------------------------------- Main Window Handlers ----------------------------------------------
|
||||
|
||||
def do_save_library(self, item):
|
||||
self.app.save_library()
|
||||
self.app.save_data()
|
||||
|
||||
def do_export_library(self, item):
|
||||
dialog = Gtk.FileChooserDialog("Export Library", self.app.ui.get_object("mainWindow"),
|
||||
@@ -88,19 +88,32 @@ class Handlers(SearchHandlers, LibraryHandlers, WantsHandlers):
|
||||
response = self.app.show_question_dialog("Unsaved Changes", "You have unsaved changes in your library. "
|
||||
"Save before exiting?")
|
||||
if response == Gtk.ResponseType.YES:
|
||||
self.app.save_library()
|
||||
self.app.save_data()
|
||||
return False
|
||||
elif response == Gtk.ResponseType.CANCEL:
|
||||
return True
|
||||
|
||||
# ---------------------- Debug actions -------------------------------
|
||||
|
||||
def do_load_lib_to_db(self, menu_item):
|
||||
def do_load_data_to_db(self, 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())
|
||||
|
||||
for card in self.app.library.values():
|
||||
self.app.db.add_card_to_lib(card)
|
||||
|
||||
for tag, card_ids in self.app.tags.items():
|
||||
self.app.db.add_tag(tag)
|
||||
for card_id in card_ids:
|
||||
self.app.db.tag_card(tag, card_id)
|
||||
|
||||
for list_name, cards in self.app.wants.items():
|
||||
self.app.db.add_wants_list(list_name)
|
||||
for card in cards:
|
||||
self.app.db.add_card_to_wants(list_name, card.multiverse_id)
|
||||
|
||||
end = datetime.datetime.now()
|
||||
util.log("Finished in {}s".format(str(end-start)), util.LogLevel.Info)
|
||||
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)
|
||||
@@ -123,3 +136,8 @@ class Handlers(SearchHandlers, LibraryHandlers, WantsHandlers):
|
||||
util.log("Deleting all local card data", util.LogLevel.Info)
|
||||
self.app.db.clear_card_data()
|
||||
util.log("Done", util.LogLevel.Info)
|
||||
|
||||
def do_clear_data(self, item):
|
||||
util.log("Deleting all library data", util.LogLevel.Info)
|
||||
self.app.db.clear_database()
|
||||
util.log("Done", util.LogLevel.Info)
|
||||
Reference in New Issue
Block a user