mirror of
https://github.com/ldericher/fftcgtool
synced 2025-01-15 06:53:00 +00:00
Mutable and Immutable CardDB variants based on click options
This commit is contained in:
parent
d585a3284d
commit
52651edca8
4 changed files with 145 additions and 88 deletions
|
@ -1,7 +1,7 @@
|
|||
from .book import Book
|
||||
from .carddb import CardDB
|
||||
from .carddb import CardDB, RWCardDB
|
||||
from .language import Language
|
||||
from .opus import Opus
|
||||
from .ttsdeck import TTSDeck
|
||||
|
||||
__all__ = ["Book", "CardDB", "Language", "Opus", "TTSDeck"]
|
||||
__all__ = ["Book", "CardDB", "RWCardDB", "Language", "Opus", "TTSDeck"]
|
||||
|
|
128
fftcg/carddb.py
128
fftcg/carddb.py
|
@ -1,93 +1,121 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import json
|
||||
import pickle
|
||||
import zipfile
|
||||
from os import PathLike
|
||||
from typing import IO
|
||||
|
||||
import requests
|
||||
|
||||
from .card import Card
|
||||
from .cards import Cards
|
||||
from .code import Code
|
||||
from .language import API_LANGS
|
||||
from .utils import CARDDB_FILE_NAME
|
||||
|
||||
|
||||
class CardDB:
|
||||
__instance: CardDB = None
|
||||
__cards: dict[Code, Card]
|
||||
__face_to_url: dict[str, str]
|
||||
_instance: CardDB = None
|
||||
_cards: dict[Code, Card]
|
||||
_face_to_url: dict[str, str]
|
||||
|
||||
__DB_FILE_NAME = "cards.pickle"
|
||||
__MAPPING_FILE_NAME = "face_to_url.json"
|
||||
_DB_FILE_NAME = "cards.pickle"
|
||||
_MAPPING_FILE_NAME = "face_to_url.json"
|
||||
|
||||
def __new__(cls) -> CardDB:
|
||||
if CardDB.__instance is None:
|
||||
CardDB.__instance = object.__new__(cls)
|
||||
CardDB.__instance.__cards = {}
|
||||
CardDB.__instance.__face_to_url = {}
|
||||
def __new__(cls, *more) -> CardDB:
|
||||
if CardDB._instance is None:
|
||||
CardDB._instance = object.__new__(CardDB)
|
||||
|
||||
return CardDB.__instance
|
||||
return CardDB._instance
|
||||
|
||||
def __init__(self, db_url: str = None):
|
||||
if db_url is not None:
|
||||
res = requests.get(db_url, stream=True)
|
||||
if not res.ok:
|
||||
raise ValueError("Invalid URL given to CardDB!")
|
||||
|
||||
self._load(io.BytesIO(res.content))
|
||||
|
||||
def _load(self, db: PathLike[str] | IO[bytes]):
|
||||
try:
|
||||
# unpickle db file
|
||||
with zipfile.ZipFile(db, "r") as zip_file:
|
||||
# cards db
|
||||
with zip_file.open(CardDB._DB_FILE_NAME, "r") as file:
|
||||
self._cards = pickle.load(file)
|
||||
|
||||
# face_to_url mapping
|
||||
with zip_file.open(CardDB._MAPPING_FILE_NAME, "r") as file:
|
||||
self._face_to_url = json.load(file)
|
||||
|
||||
except FileNotFoundError:
|
||||
self._cards = {}
|
||||
self._face_to_url = {}
|
||||
|
||||
def __contains__(self, item: Code) -> bool:
|
||||
return item in self.__cards
|
||||
return item in self._cards
|
||||
|
||||
def __getitem__(self, code: Code) -> Card:
|
||||
return self.__cards[code]
|
||||
return self._cards[code]
|
||||
|
||||
def get_face_url(self, face: str) -> str:
|
||||
if face in self.__face_to_url:
|
||||
return self.__face_to_url[face]
|
||||
if face in self._face_to_url:
|
||||
return self._face_to_url[face]
|
||||
else:
|
||||
return face
|
||||
|
||||
def __pickle(self) -> None:
|
||||
with zipfile.ZipFile(CARDDB_FILE_NAME, "w", compression=zipfile.ZIP_LZMA) as zip_file:
|
||||
def save(self) -> None:
|
||||
return
|
||||
|
||||
def update(self, cards: Cards) -> None:
|
||||
return
|
||||
|
||||
def upload_prompt(self) -> None:
|
||||
return
|
||||
|
||||
|
||||
class RWCardDB(CardDB):
|
||||
__db_path: PathLike[str]
|
||||
|
||||
def __new__(cls, *more) -> RWCardDB:
|
||||
if CardDB._instance is None:
|
||||
CardDB._instance = object.__new__(RWCardDB)
|
||||
|
||||
return CardDB._instance
|
||||
|
||||
def __init__(self, db_path: PathLike[str] = None):
|
||||
super().__init__(None)
|
||||
|
||||
if db_path is not None:
|
||||
self.__db_path = db_path
|
||||
self._load(self.__db_path)
|
||||
|
||||
def save(self) -> None:
|
||||
with zipfile.ZipFile(self.__db_path, "w", compression=zipfile.ZIP_LZMA) as zip_file:
|
||||
# cards db
|
||||
with zip_file.open(CardDB.__DB_FILE_NAME, "w") as file:
|
||||
pickle.dump(self.__cards, file)
|
||||
with zip_file.open(CardDB._DB_FILE_NAME, "w") as file:
|
||||
pickle.dump(self._cards, file)
|
||||
|
||||
# face_to_url mapping
|
||||
with zip_file.open(CardDB.__MAPPING_FILE_NAME, "w") as file:
|
||||
file.write(json.dumps(self.__face_to_url, indent=2).encode("utf-8"))
|
||||
|
||||
def __unpickle(self) -> None:
|
||||
# unpickle db file
|
||||
self.__cards.clear()
|
||||
self.__face_to_url.clear()
|
||||
try:
|
||||
with zipfile.ZipFile(CARDDB_FILE_NAME, "r") as zip_file:
|
||||
# cards db
|
||||
with zip_file.open(CardDB.__DB_FILE_NAME, "r") as file:
|
||||
self.__cards |= pickle.load(file)
|
||||
|
||||
# face_to_url mapping
|
||||
with zip_file.open(CardDB.__MAPPING_FILE_NAME, "r") as file:
|
||||
self.__face_to_url |= json.load(file)
|
||||
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
def load(self) -> None:
|
||||
self.__unpickle()
|
||||
with zip_file.open(CardDB._MAPPING_FILE_NAME, "w") as file:
|
||||
file.write(json.dumps(self._face_to_url, indent=2).encode("utf-8"))
|
||||
|
||||
def update(self, cards: Cards) -> None:
|
||||
for card in cards:
|
||||
self.__cards[card.code] = card
|
||||
|
||||
self.__pickle()
|
||||
self._cards[card.code] = card
|
||||
|
||||
def upload_prompt(self) -> None:
|
||||
faces = list(set([
|
||||
card[lang].face
|
||||
for card in self.__cards.values()
|
||||
for card in self._cards.values()
|
||||
for lang in API_LANGS
|
||||
if card[lang].face
|
||||
]))
|
||||
faces.sort()
|
||||
|
||||
for face in faces:
|
||||
if face not in self.__face_to_url:
|
||||
if face not in self._face_to_url:
|
||||
face_url = input(f"Upload '{face}' and paste URL: ")
|
||||
if face_url:
|
||||
self.__face_to_url[face] = face_url
|
||||
|
||||
self.__pickle()
|
||||
self._face_to_url[face] = face_url
|
||||
|
|
|
@ -10,7 +10,6 @@ GRID = Grid((10, 7)) # default in TTsim: 10 columns, 7 rows
|
|||
RESOLUTION = Grid((429, 600)) # default in TTsim: 480x670 pixels per card
|
||||
DECKS_DIR_NAME = "decks" # name of decks directory
|
||||
IMAGES_DIR_NAME = "images" # name of images directory
|
||||
CARDDB_FILE_NAME = "carddb.zip" # name of card db file
|
||||
# card back URL (image by Aurik)
|
||||
CARD_BACK_URL = "http://cloud-3.steamusercontent.com/ugc/948455238665576576/85063172B8C340602E8D6C783A457122F53F7843/"
|
||||
|
||||
|
|
100
fftcgtool.py
100
fftcgtool.py
|
@ -8,13 +8,8 @@ import click
|
|||
|
||||
import fftcg
|
||||
|
||||
# constants
|
||||
OUT_DIR_NAME = "out" # name of output directory
|
||||
|
||||
|
||||
class LanguageParamType(click.ParamType):
|
||||
name = "lang"
|
||||
|
||||
def convert(self, value, param, ctx) -> fftcg.Language:
|
||||
if isinstance(value, fftcg.Language):
|
||||
return value
|
||||
|
@ -38,23 +33,53 @@ LANGUAGE = LanguageParamType()
|
|||
type=LANGUAGE,
|
||||
default="en",
|
||||
help="language for imported objects",
|
||||
metavar="LANG",
|
||||
)
|
||||
@click.option(
|
||||
"-s", "--stdout",
|
||||
is_flag=True,
|
||||
help="print the deck files in a zip archive to stdout, skip creating JSONs on disk",
|
||||
"-z", "--zip",
|
||||
type=click.File("wb"),
|
||||
help="wrap deck files into a zip archive, skip creating individual JSONs",
|
||||
metavar="FILE",
|
||||
)
|
||||
@click.option(
|
||||
"-o", "--output",
|
||||
type=click.Path(
|
||||
allow_dash=False,
|
||||
dir_okay=True,
|
||||
file_okay=False,
|
||||
),
|
||||
help="use specified output directory instead of ./out",
|
||||
default="out",
|
||||
metavar="DIR",
|
||||
)
|
||||
@click.option(
|
||||
"-u", "--db-url",
|
||||
type=str,
|
||||
help="load immutable CardDB from URL instead of local, overrides -f",
|
||||
metavar="URL",
|
||||
)
|
||||
@click.option(
|
||||
"-f", "--db-file",
|
||||
type=click.Path(
|
||||
allow_dash=False,
|
||||
dir_okay=False,
|
||||
file_okay=True,
|
||||
),
|
||||
default="carddb.zip",
|
||||
help="use specified CardDB file instead of ./out/carddb.zip",
|
||||
metavar="FILE",
|
||||
)
|
||||
@click.pass_context
|
||||
def main(ctx, verbose, language, stdout) -> None:
|
||||
def main(ctx, **kwargs) -> None:
|
||||
"""Imports FFTCG cards for TT-Sim."""
|
||||
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj['LANG'] = language
|
||||
ctx.obj["language"] = kwargs["language"]
|
||||
|
||||
# set up logging
|
||||
if verbose == 0:
|
||||
if kwargs["verbose"] == 0:
|
||||
verbose = logging.WARN
|
||||
elif verbose == 1:
|
||||
elif kwargs["verbose"] == 1:
|
||||
verbose = logging.INFO
|
||||
else:
|
||||
verbose = logging.DEBUG
|
||||
|
@ -66,31 +91,39 @@ def main(ctx, verbose, language, stdout) -> None:
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.info("fftcgtool started.")
|
||||
logger.debug(f"args: {verbose = }, {language = }, {stdout = }")
|
||||
logger.debug(f"{kwargs = }")
|
||||
|
||||
# output directory
|
||||
if not os.path.exists(OUT_DIR_NAME):
|
||||
os.mkdir(OUT_DIR_NAME)
|
||||
if not os.path.exists(kwargs["output"]):
|
||||
os.mkdir(kwargs["output"])
|
||||
|
||||
os.chdir(OUT_DIR_NAME)
|
||||
os.chdir(kwargs["output"])
|
||||
|
||||
# load the current carddb
|
||||
carddb = fftcg.CardDB()
|
||||
carddb.load()
|
||||
if kwargs["db_url"] is not None:
|
||||
try:
|
||||
fftcg.CardDB(kwargs["db_url"])
|
||||
|
||||
except (ValueError, KeyError, zipfile.BadZipFile) as cause:
|
||||
logger.critical(f"Couldn't initialize CardDB: {cause}")
|
||||
sys.exit(1)
|
||||
|
||||
else:
|
||||
fftcg.RWCardDB(kwargs["db_file"])
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option(
|
||||
"-n", "--num_requests",
|
||||
"-n", "--num-requests",
|
||||
type=int,
|
||||
default=20,
|
||||
help="maximum number of concurrent requests",
|
||||
)
|
||||
@click.argument(
|
||||
"opus_ids",
|
||||
"opus-ids",
|
||||
nargs=-1,
|
||||
type=str,
|
||||
metavar="[OPUS_ID] ...",
|
||||
metavar="[OPUS-ID] ...",
|
||||
)
|
||||
@click.pass_context
|
||||
def opuses(ctx, opus_ids, num_requests) -> list[fftcg.TTSDeck]:
|
||||
|
@ -101,7 +134,7 @@ def opuses(ctx, opus_ids, num_requests) -> list[fftcg.TTSDeck]:
|
|||
"""
|
||||
|
||||
ctx.ensure_object(dict)
|
||||
language = ctx.obj['LANG'] or fftcg.Language("")
|
||||
language = ctx.obj["language"] or fftcg.Language("")
|
||||
|
||||
carddb = fftcg.CardDB()
|
||||
decks: list[fftcg.TTSDeck] = []
|
||||
|
@ -115,6 +148,7 @@ def opuses(ctx, opus_ids, num_requests) -> list[fftcg.TTSDeck]:
|
|||
decks.extend(opus.elemental_decks)
|
||||
|
||||
carddb.upload_prompt()
|
||||
carddb.save()
|
||||
|
||||
# create elemental decks for opus
|
||||
return decks
|
||||
|
@ -122,10 +156,10 @@ def opuses(ctx, opus_ids, num_requests) -> list[fftcg.TTSDeck]:
|
|||
|
||||
@main.command()
|
||||
@click.argument(
|
||||
"deck_ids",
|
||||
"deck-ids",
|
||||
nargs=-1,
|
||||
type=str,
|
||||
metavar="[DECK_ID] ...",
|
||||
metavar="[DECK-ID] ...",
|
||||
)
|
||||
def ffdecks(deck_ids) -> list[fftcg.TTSDeck]:
|
||||
"""
|
||||
|
@ -134,7 +168,6 @@ def ffdecks(deck_ids) -> list[fftcg.TTSDeck]:
|
|||
DECK_ID: each of the Decks to import
|
||||
"""
|
||||
|
||||
print(f"{deck_ids = }")
|
||||
decks: list[fftcg.TTSDeck] = []
|
||||
for deck_id in deck_ids:
|
||||
# import a deck
|
||||
|
@ -144,23 +177,20 @@ def ffdecks(deck_ids) -> list[fftcg.TTSDeck]:
|
|||
|
||||
|
||||
@main.result_callback()
|
||||
def process_decks(decks: list[fftcg.TTSDeck], verbose, language, stdout):
|
||||
# arg needed because it's in this group
|
||||
int(verbose)
|
||||
|
||||
def finalize(decks: list[fftcg.TTSDeck], **kwargs):
|
||||
# decide what to do with the decks
|
||||
if stdout:
|
||||
# print out a zip file
|
||||
with open(sys.stdout.fileno(), "wb", closefd=False, buffering=0) as raw_stdout:
|
||||
with zipfile.ZipFile(raw_stdout, "w", compression=zipfile.ZIP_DEFLATED) as zip_file:
|
||||
if kwargs["zip"] is not None:
|
||||
if decks:
|
||||
# create zip file
|
||||
with zipfile.ZipFile(kwargs["zip"], "w", compression=zipfile.ZIP_DEFLATED) as zip_file:
|
||||
# put the decks into that zip file
|
||||
for deck in decks:
|
||||
zip_file.writestr(deck.file_name, deck.get_json(language))
|
||||
zip_file.writestr(deck.file_name, deck.get_json(kwargs["language"]))
|
||||
|
||||
else:
|
||||
# save the decks to disk
|
||||
for deck in decks:
|
||||
deck.save(language)
|
||||
deck.save(kwargs["language"])
|
||||
|
||||
# bye
|
||||
print("Done. Put the generated JSON files in your 'Saved Objects' Folder.")
|
||||
|
|
Loading…
Reference in a new issue