Files
cardvault/legacy/util.py
2018-02-19 22:56:32 +01:00

421 lines
12 KiB
Python

import copy
import enum
import json
import jsonpickle
import os
import re
import sys
import six.moves.cPickle as pickle
from time import localtime, strftime, time
from PIL import Image as PImage
import urllib.request
from urllib import request
from mtgsdk import Set, Card, MtgException
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GdkPixbuf, GLib
# Title of the Program Window
APPLICATION_TITLE = "Card Vault"
# Program version
VERSION = "0.6.0"
# Path of image cache
CACHE_PATH = os.path.expanduser('~') + "/.cardvault/"
IMAGE_CACHE_PATH = os.path.expanduser('~') + "/.cardvault/images/"
ICON_CACHE_PATH = os.path.expanduser('~') + "/.cardvault/icons/"
# Log level of the application
# 1 Info
# 2 Warning
# 3 Error
LOG_LEVEL = 1
# Name of the database
DB_NAME = "cardvault.db"
ALL_NUM_URL = 'https://api.magicthegathering.io/v1/cards?page=0&pageSize=100'
ALL_SETS_JSON_URL = 'https://mtgjson.com/json/AllSets-x.json'
# URL for card images. Insert card.multiverse_id.
CARD_IMAGE_URL = 'http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid={}&type=card'
# Location of manual wiki
MANUAL_LOCATION = 'https://github.com/luxick/cardvault'
# Colors for card rows in search view
SEARCH_TREE_COLORS = {
"unowned": "black",
"wanted": "#D39F30",
"owned": "#62B62F"
}
# Colors for card rows in every default view
GENERIC_TREE_COLORS = {
"unowned": "black",
"wanted": "black",
"owned": "black"
}
LEGALITY_COLORS = {
"Banned": "#C65642",
"Restricted": "#D39F30",
"Legal": "#62B62F"
}
DEFAULT_CONFIG = {
"last_viewed": "",
"show_all_in_search": True,
"start_page": "search",
"local_db": False,
"first_run": True,
"log_level": 3
}
card_colors = {
'White': 'W',
'Blue': 'U',
'Black': 'B',
'Red': 'R',
'Green': 'G'
}
color_sort_order = {
'W': 0,
'U': 1,
'B': 2,
'R': 3,
'G': 4
}
rarity_dict = {
"special": 0,
"common": 1,
"uncommon": 2,
"rare": 3,
"mythic rare": 4
}
card_types = ["Creature", "Artifact", "Instant", "Enchantment", "Sorcery", "Land", "Planeswalker"]
online_icons = {
True: 'network-wired',
False: 'drive-harddisk'
}
online_tooltips = {
True: 'Using online card data',
False: 'Using card data from local database.'
}
class LogLevel(enum.Enum):
Error = 1
Warning = 2
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(msg: str, ll: LogLevel):
if ll.value <= LOG_LEVEL:
lv = "[" + ll.name + "] "
if ll.value == 2:
c = TerminalColors.WARNING
elif ll.value == 1:
c = TerminalColors.BOLD + TerminalColors.FAIL
else:
c = ""
tc = strftime("%H:%M:%S ", localtime())
print(c + lv + tc + msg + TerminalColors.ENDC)
def parse_config(filename: str, default: dict):
config = copy.copy(default)
try:
with open(filename) as configfile:
loaded_config = json.load(configfile)
config.update(loaded_config)
except IOError:
# Will just use the default config
# and create the file for manual editing
save_config(config, filename)
except ValueError:
# There's a syntax error in the config file
log("Syntax error wihle parsing config file", LogLevel.Error)
return
return config
def save_config(config: dict, filename: str):
path = os.path.dirname(filename)
if not os.path.isdir(path):
os.mkdir(path)
with open(filename, 'wb') as configfile:
configfile.write(json.dumps(config, sort_keys=True,
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.join(os.path.dirname(__file__), 'gui', filename)
def reload_image_cache(path: str) -> dict:
cache = {}
if not os.path.isdir(path):
os.mkdir(path)
imagefiles = os.listdir(path)
for imagefile in imagefiles:
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(path + imagefile)
# Strip filename extension
imagename = os.path.splitext(imagefile)[0]
cache[int(imagename)] = pixbuf
except OSError as err:
log("Error loading image: " + str(err), LogLevel.Error)
except GLib.GError as err:
log("Error loading image: " + str(err), LogLevel.Error)
return cache
def reload_preconstructed_icons(path: str) -> dict:
cache = {}
if not os.path.exists(path):
os.makedirs(path)
files = os.listdir(path)
for file in files:
# Split filename into single icon names and remove extension
without_ext = file.split(".")[0]
names = without_ext.split("_")
# Compute size of the finished icon
pic_width = len(names) * 105
pic_height = 105
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(ICON_CACHE_PATH + file)
pixbuf = pixbuf.scale_simple(pic_width / 5, pic_height / 5, GdkPixbuf.InterpType.HYPER)
# Set name for icon
iconname = "_".join(names)
cache[iconname] = pixbuf
except OSError as err:
log("Error loading image: " + str(err), LogLevel.Error)
return cache
def load_mana_icons(path: str) -> dict:
if not os.path.exists(path):
log("Directory for mana icons not found " + path, LogLevel.Error)
return {}
icons = {}
files = os.listdir(path)
for file in files:
img = PImage.open(path + file)
# Strip file extension
name = os.path.splitext(file)[0]
icons[name] = img
return icons
def net_all_cards_mtgjson() -> dict:
with urllib.request.urlopen(ALL_SETS_JSON_URL) as url:
data = json.loads(url.read().decode())
return data
def net_load_set_list() -> dict:
""" Load the list of all MTG sets from the Gather"""
try:
start = time()
sets = Set.all()
stop = time()
log("Fetched set list in {}s".format(round(stop - start, 3)), LogLevel.Info)
except MtgException as err:
log(str(err), LogLevel.Error)
return {}
return [set.__dict__ for set in sets]
def load_sets(filename: str) -> dict:
"""
Load sets from local file if possible.
Called by: Application if in online mode
"""
if not os.path.isfile(filename):
# use mtgsdk api to retrieve al list of all sets
sets = net_load_set_list()
# Serialize the loaded data to a file
pickle.dump(sets, open(filename, 'wb'))
# Deserialize set data from local file
sets = pickle.load(open(filename, 'rb'))
# Sort the loaded sets based on the sets name
output = {}
for set in sorted(sets, key=lambda x: x.get('name')):
output[set.get('code')] = set
return output
def export_library(path, file):
try:
pickle.dump(file, open(path, 'wb'))
log("Library exported to \"" + path + "\"", LogLevel.Info)
except OSError as err:
log(str(err), LogLevel.Error)
def export_json(path, file):
"""Write file in json format"""
try:
f = open(path, 'w')
s = jsonpickle.encode(file)
f.write(s)
except OSError as err:
log(str(err), LogLevel.Error)
def import_library(path: str) -> ():
try:
imported = pickle.load(open(path, 'rb'))
except pickle.UnpicklingError as err:
log(str(err) + " while importing", LogLevel.Error)
return
# Parse imported file
try:
library = imported["library"]
tags = imported["tags"]
wants = imported["wants"]
except KeyError as err:
log("Invalid library format " + str(err), LogLevel.Error)
library = {}
tags = {}
wants = {}
log("Library imported", LogLevel.Info)
return library, tags, wants
def save_file(path, file):
# Serialize using cPickle
try:
pickle.dump(file, open(path, 'wb'))
except OSError as err:
log(str(err), LogLevel.Error)
return
log("Saved file " + path, LogLevel.Info)
def load_file(path: str):
if not os.path.isfile(path):
log(path + " does not exist", LogLevel.Warning)
return
try:
loaded = pickle.load(open(path, 'rb'))
except OSError as err:
log(str(err), LogLevel.Error)
return
return loaded
def load_dummy_image(size_x: int, size_y: int) -> GdkPixbuf:
return GdkPixbuf.Pixbuf.new_from_file_at_size(os.path.dirname(__file__)
+ '/resources/images/dummy.jpg', size_x, size_y)
def load_card_image(card: dict, size_x: int, size_y: int, cache: dict) -> GdkPixbuf:
""" Retrieve an card image from cache or alternatively load from gatherer"""
try:
image = cache[card.get('multiverse_id')]
except KeyError:
log("No local image for " + card.get('name') + ". Loading from " + card.get('image_url'), LogLevel.Info)
filename, image = net_load_card_image(card, size_x, size_y)
cache[card.get('multiverse_id')] = image
return image
def net_load_card_image(card: dict, size_x: int, size_y: int) -> (str, GdkPixbuf):
url = card.get('image_url')
if url is None:
log("No Image URL for " + card.get('name'), LogLevel.Warning)
return load_dummy_image(size_x, size_y)
filename = IMAGE_CACHE_PATH + str(card.get('multiverse_id')) + ".png"
request.urlretrieve(url, filename)
return filename, GdkPixbuf.Pixbuf.new_from_file_at_size(filename, size_x, size_y)
def create_mana_icons(icons: dict, mana_string: str) -> GdkPixbuf:
# Convert the string to a List
safe_string = mana_string.replace("/", "-")
glyphs = re.findall("{(.*?)}", safe_string)
if len(glyphs) == 0:
return
# Compute horizontal size for the final image
size = len(glyphs) * 105
image = PImage.new("RGBA", (size, 105))
# Increment for each position of an icon
# (Workaround: 2 or more of the same icon will be rendered in the same position)
c = 0
# Go through all entries an add the correspondent icon to the final image
for icon in glyphs:
x_pos = c * 105
try:
loaded = icons[icon]
except KeyError:
log("No icon file named '" + icon + "' found.", LogLevel.Warning)
return
image.paste(loaded, (x_pos, 0))
c += 1
# Save Icon file
path = ICON_CACHE_PATH + "_".join(glyphs) + ".png"
image.save(path)
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(path)
pixbuf = pixbuf.scale_simple(image.width / 5, image.height / 5, GdkPixbuf.InterpType.HYPER)
except:
return
return pixbuf
def unique_list(seq):
seen = set()
seen_add = seen.add
return [x for x in seq if not (x in seen or seen_add(x))]
def sizeof_fmt(num, suffix='B'):
for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
if abs(num) < 1024.0:
return "%3.1f%s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, 'Yi', suffix)
def get_all_cards_num() -> int:
req = urllib.request.Request(ALL_NUM_URL, headers={'User-Agent': 'Mozilla/5.0'})
response = urllib.request.urlopen(req)
headers = response.info()._headers
for header, value in headers:
if header == 'Total-Count':
return int(value)