Initial commit of mtgonline project
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Simple routing to extract a single file from a xz archive
|
||||
* Heavily based from doc/examples/02_decompress.c obtained from
|
||||
* the official xz git repository: git.tukaani.org/xz.git
|
||||
* The license from the original file header follows
|
||||
*
|
||||
* Author: Lasse Collin
|
||||
* This file has been put into the public domain.
|
||||
* You can do whatever you want with this file.
|
||||
*/
|
||||
|
||||
|
||||
#include <lzma.h>
|
||||
#include <QDebug>
|
||||
|
||||
#include "decompress.h"
|
||||
|
||||
XzDecompressor::XzDecompressor(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool XzDecompressor::decompress(QBuffer *in, QBuffer *out)
|
||||
{
|
||||
lzma_stream strm = LZMA_STREAM_INIT;
|
||||
bool success;
|
||||
|
||||
if (!init_decoder(&strm)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
success = internal_decompress(&strm, in, out);
|
||||
|
||||
// Free the memory allocated for the decoder. This only needs to be
|
||||
// done after the last file.
|
||||
lzma_end(&strm);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool XzDecompressor::init_decoder(lzma_stream *strm)
|
||||
{
|
||||
// Initialize a .xz decoder. The decoder supports a memory usage limit
|
||||
// and a set of flags.
|
||||
//
|
||||
// The memory usage of the decompressor depends on the settings used
|
||||
// to compress a .xz file. It can vary from less than a megabyte to
|
||||
// a few gigabytes, but in practice (at least for now) it rarely
|
||||
// exceeds 65 MiB because that's how much memory is required to
|
||||
// decompress files created with "xz -9". Settings requiring more
|
||||
// memory take extra effort to use and don't (at least for now)
|
||||
// provide significantly better compression in most cases.
|
||||
//
|
||||
// Memory usage limit is useful if it is important that the
|
||||
// decompressor won't consume gigabytes of memory. The need
|
||||
// for limiting depends on the application. In this example,
|
||||
// no memory usage limiting is used. This is done by setting
|
||||
// the limit to UINT64_MAX.
|
||||
//
|
||||
// The .xz format allows concatenating compressed files as is:
|
||||
//
|
||||
// echo foo | xz > foobar.xz
|
||||
// echo bar | xz >> foobar.xz
|
||||
//
|
||||
// When decompressing normal standalone .xz files, LZMA_CONCATENATED
|
||||
// should always be used to support decompression of concatenated
|
||||
// .xz files. If LZMA_CONCATENATED isn't used, the decoder will stop
|
||||
// after the first .xz stream. This can be useful when .xz data has
|
||||
// been embedded inside another file format.
|
||||
//
|
||||
// Flags other than LZMA_CONCATENATED are supported too, and can
|
||||
// be combined with bitwise-or. See lzma/container.h
|
||||
// (src/liblzma/api/lzma/container.h in the source package or e.g.
|
||||
// /usr/include/lzma/container.h depending on the install prefix)
|
||||
// for details.
|
||||
lzma_ret ret = lzma_stream_decoder(
|
||||
strm, UINT64_MAX, LZMA_CONCATENATED);
|
||||
|
||||
// Return successfully if the initialization went fine.
|
||||
if (ret == LZMA_OK)
|
||||
return true;
|
||||
|
||||
// Something went wrong. The possible errors are documented in
|
||||
// lzma/container.h (src/liblzma/api/lzma/container.h in the source
|
||||
// package or e.g. /usr/include/lzma/container.h depending on the
|
||||
// install prefix).
|
||||
//
|
||||
// Note that LZMA_MEMLIMIT_ERROR is never possible here. If you
|
||||
// specify a very tiny limit, the error will be delayed until
|
||||
// the first headers have been parsed by a call to lzma_code().
|
||||
const char *msg;
|
||||
switch (ret) {
|
||||
case LZMA_MEM_ERROR:
|
||||
msg = "Memory allocation failed";
|
||||
break;
|
||||
|
||||
case LZMA_OPTIONS_ERROR:
|
||||
msg = "Unsupported decompressor flags";
|
||||
break;
|
||||
|
||||
default:
|
||||
// This is most likely LZMA_PROG_ERROR indicating a bug in
|
||||
// this program or in liblzma. It is inconvenient to have a
|
||||
// separate error message for errors that should be impossible
|
||||
// to occur, but knowing the error code is important for
|
||||
// debugging. That's why it is good to print the error code
|
||||
// at least when there is no good error message to show.
|
||||
msg = "Unknown error, possibly a bug";
|
||||
break;
|
||||
}
|
||||
|
||||
qDebug() << "Error initializing the decoder:" << msg << "(error code " << ret << ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool XzDecompressor::internal_decompress(lzma_stream *strm, QBuffer *in, QBuffer *out)
|
||||
{
|
||||
// When LZMA_CONCATENATED flag was used when initializing the decoder,
|
||||
// we need to tell lzma_code() when there will be no more input.
|
||||
// This is done by setting action to LZMA_FINISH instead of LZMA_RUN
|
||||
// in the same way as it is done when encoding.
|
||||
//
|
||||
// When LZMA_CONCATENATED isn't used, there is no need to use
|
||||
// LZMA_FINISH to tell when all the input has been read, but it
|
||||
// is still OK to use it if you want. When LZMA_CONCATENATED isn't
|
||||
// used, the decoder will stop after the first .xz stream. In that
|
||||
// case some unused data may be left in strm->next_in.
|
||||
lzma_action action = LZMA_RUN;
|
||||
|
||||
uint8_t inbuf[BUFSIZ];
|
||||
uint8_t outbuf[BUFSIZ];
|
||||
qint64 bytesAvailable;
|
||||
|
||||
strm->next_in = NULL;
|
||||
strm->avail_in = 0;
|
||||
strm->next_out = outbuf;
|
||||
strm->avail_out = sizeof(outbuf);
|
||||
while (true) {
|
||||
if (strm->avail_in == 0) {
|
||||
strm->next_in = inbuf;
|
||||
bytesAvailable = in->bytesAvailable();
|
||||
if(bytesAvailable == 0) {
|
||||
// Once the end of the input file has been reached,
|
||||
// we need to tell lzma_code() that no more input
|
||||
// will be coming. As said before, this isn't required
|
||||
// if the LZMA_CONCATENATED flag isn't used when
|
||||
// initializing the decoder.
|
||||
action = LZMA_FINISH;
|
||||
} else if(bytesAvailable >= BUFSIZ) {
|
||||
in->read((char*) inbuf, BUFSIZ);
|
||||
strm->avail_in = BUFSIZ;
|
||||
} else {
|
||||
in->read((char*) inbuf, bytesAvailable);
|
||||
strm->avail_in = bytesAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
lzma_ret ret = lzma_code(strm, action);
|
||||
|
||||
if (strm->avail_out == 0 || ret == LZMA_STREAM_END) {
|
||||
qint64 write_size = sizeof(outbuf) - strm->avail_out;
|
||||
|
||||
if (out->write((char *) outbuf, write_size) != write_size) {
|
||||
qDebug() << "Write error";
|
||||
return false;
|
||||
}
|
||||
|
||||
strm->next_out = outbuf;
|
||||
strm->avail_out = sizeof(outbuf);
|
||||
}
|
||||
|
||||
if (ret != LZMA_OK) {
|
||||
// Once everything has been decoded successfully, the
|
||||
// return value of lzma_code() will be LZMA_STREAM_END.
|
||||
//
|
||||
// It is important to check for LZMA_STREAM_END. Do not
|
||||
// assume that getting ret != LZMA_OK would mean that
|
||||
// everything has gone well or that when you aren't
|
||||
// getting more output it must have successfully
|
||||
// decoded everything.
|
||||
if (ret == LZMA_STREAM_END)
|
||||
return true;
|
||||
|
||||
// It's not LZMA_OK nor LZMA_STREAM_END,
|
||||
// so it must be an error code. See lzma/base.h
|
||||
// (src/liblzma/api/lzma/base.h in the source package
|
||||
// or e.g. /usr/include/lzma/base.h depending on the
|
||||
// install prefix) for the list and documentation of
|
||||
// possible values. Many values listen in lzma_ret
|
||||
// enumeration aren't possible in this example, but
|
||||
// can be made possible by enabling memory usage limit
|
||||
// or adding flags to the decoder initialization.
|
||||
const char *msg;
|
||||
switch (ret) {
|
||||
case LZMA_MEM_ERROR:
|
||||
msg = "Memory allocation failed";
|
||||
break;
|
||||
|
||||
case LZMA_FORMAT_ERROR:
|
||||
// .xz magic bytes weren't found.
|
||||
msg = "The input is not in the .xz format";
|
||||
break;
|
||||
|
||||
case LZMA_OPTIONS_ERROR:
|
||||
// For example, the headers specify a filter
|
||||
// that isn't supported by this liblzma
|
||||
// version (or it hasn't been enabled when
|
||||
// building liblzma, but no-one sane does
|
||||
// that unless building liblzma for an
|
||||
// embedded system). Upgrading to a newer
|
||||
// liblzma might help.
|
||||
//
|
||||
// Note that it is unlikely that the file has
|
||||
// accidentally became corrupt if you get this
|
||||
// error. The integrity of the .xz headers is
|
||||
// always verified with a CRC32, so
|
||||
// unintentionally corrupt files can be
|
||||
// distinguished from unsupported files.
|
||||
msg = "Unsupported compression options";
|
||||
break;
|
||||
|
||||
case LZMA_DATA_ERROR:
|
||||
msg = "Compressed file is corrupt";
|
||||
break;
|
||||
|
||||
case LZMA_BUF_ERROR:
|
||||
// Typically this error means that a valid
|
||||
// file has got truncated, but it might also
|
||||
// be a damaged part in the file that makes
|
||||
// the decoder think the file is truncated.
|
||||
// If you prefer, you can use the same error
|
||||
// message for this as for LZMA_DATA_ERROR.
|
||||
msg = "Compressed file is truncated or "
|
||||
"otherwise corrupt";
|
||||
break;
|
||||
|
||||
default:
|
||||
// This is most likely LZMA_PROG_ERROR.
|
||||
msg = "Unknown error, possibly a bug";
|
||||
break;
|
||||
}
|
||||
|
||||
qDebug() << "Decoder error:" << msg << "(error code " << ret << ")";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef XZ_DECOMPRESS_H
|
||||
#define XZ_DECOMPRESS_H
|
||||
|
||||
#include <lzma.h>
|
||||
#include <QBuffer>
|
||||
|
||||
class XzDecompressor : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
XzDecompressor(QObject *parent = 0);
|
||||
~XzDecompressor() { };
|
||||
bool decompress(QBuffer *in, QBuffer *out);
|
||||
private:
|
||||
bool init_decoder(lzma_stream *strm);
|
||||
bool internal_decompress(lzma_stream *strm, QBuffer *in, QBuffer *out);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,98 @@
|
||||
#include "main.h"
|
||||
|
||||
#include "interface/theme_manager.h"
|
||||
#include "oraclewizard.h"
|
||||
|
||||
#include <../../cockatrice/src/client/settings/cache_settings.h>
|
||||
#include <QApplication>
|
||||
#include <QCommandLineParser>
|
||||
#include <QIcon>
|
||||
#include <QLibraryInfo>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
|
||||
QTranslator *translator, *qtTranslator;
|
||||
ThemeManager *themeManager;
|
||||
|
||||
const QString translationPrefix = "oracle";
|
||||
QString translationPath;
|
||||
bool isSpoilersOnly;
|
||||
bool isBackgrounded;
|
||||
|
||||
void installNewTranslator()
|
||||
{
|
||||
QString lang = SettingsCache::instance().getLang();
|
||||
|
||||
QString qtNameHint = "qt_" + lang;
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
QString qtTranslationPath = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
|
||||
#else
|
||||
QString qtTranslationPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
|
||||
#endif
|
||||
|
||||
bool qtTranslationLoaded = qtTranslator->load(qtNameHint, qtTranslationPath);
|
||||
if (!qtTranslationLoaded) {
|
||||
qDebug() << "Unable to load qt translation" << qtNameHint << "at" << qtTranslationPath;
|
||||
} else {
|
||||
qDebug() << "Loaded qt translation" << qtNameHint << "at" << qtTranslationPath;
|
||||
}
|
||||
qApp->installTranslator(qtTranslator);
|
||||
|
||||
QString appNameHint = translationPrefix + "_" + lang;
|
||||
bool appTranslationLoaded = qtTranslator->load(appNameHint, translationPath);
|
||||
if (!appTranslationLoaded) {
|
||||
qDebug() << "Unable to load" << translationPrefix << "translation" << appNameHint << "at" << translationPath;
|
||||
} else {
|
||||
qDebug() << "Loaded" << translationPrefix << "translation" << appNameHint << "at" << translationPath;
|
||||
}
|
||||
qApp->installTranslator(translator);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
QCoreApplication::setOrganizationName("Cockatrice");
|
||||
QCoreApplication::setOrganizationDomain("cockatrice");
|
||||
// this can't be changed, as it influences the default save path for cards.xml
|
||||
QCoreApplication::setApplicationName("Cockatrice");
|
||||
|
||||
// If the program is opened with the -s flag, it will only do spoilers. Otherwise it will do MTGJSON/Tokens
|
||||
QCommandLineParser parser;
|
||||
QCommandLineOption spoilersOnlyOption("s", QCoreApplication::translate("main", "Only run in spoiler mode"));
|
||||
QCommandLineOption backgroundOption("b", QCoreApplication::translate("main", "Run in no-confirm background mode"));
|
||||
parser.addOption(spoilersOnlyOption);
|
||||
parser.addOption(backgroundOption);
|
||||
parser.process(app);
|
||||
isSpoilersOnly = parser.isSet(spoilersOnlyOption);
|
||||
isBackgrounded = parser.isSet(backgroundOption);
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
translationPath = qApp->applicationDirPath() + "/../Resources/translations";
|
||||
#elif defined(Q_OS_WIN)
|
||||
translationPath = qApp->applicationDirPath() + "/translations";
|
||||
#else // linux
|
||||
translationPath = qApp->applicationDirPath() + "/../share/oracle/translations";
|
||||
#endif
|
||||
|
||||
themeManager = new ThemeManager;
|
||||
|
||||
qtTranslator = new QTranslator;
|
||||
translator = new QTranslator;
|
||||
installNewTranslator();
|
||||
|
||||
OracleWizard wizard;
|
||||
|
||||
QIcon icon("theme:appicon.svg");
|
||||
wizard.setWindowIcon(icon);
|
||||
// set name of the app desktop file; used by wayland to load the window icon
|
||||
QGuiApplication::setDesktopFileName("oracle");
|
||||
|
||||
wizard.show();
|
||||
|
||||
if (isBackgrounded) {
|
||||
QTimer::singleShot(0, &wizard, [&wizard]() { wizard.runInBackground(); });
|
||||
}
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef MAIN_H
|
||||
#define MAIN_H
|
||||
|
||||
class QTranslator;
|
||||
class QString;
|
||||
|
||||
extern QTranslator *translator;
|
||||
extern const QString translationPrefix;
|
||||
extern QString translationPath;
|
||||
extern bool isSpoilersOnly;
|
||||
|
||||
void installNewTranslator();
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,580 @@
|
||||
#include "oracleimporter.h"
|
||||
|
||||
#include "libcockatrice/interfaces/noop_card_preference_provider.h"
|
||||
#include "libcockatrice/interfaces/noop_card_set_priority_controller.h"
|
||||
#include "parsehelpers.h"
|
||||
#include "qt-json/json.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QRegularExpression>
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <libcockatrice/card/database/parser/cockatrice_xml_4.h>
|
||||
#include <libcockatrice/card/relation/card_relation.h>
|
||||
|
||||
static const QList<AllowedCount> kConstructedCounts = {{4, "legal"}, {0, "banned"}};
|
||||
|
||||
static const QList<AllowedCount> kSingletonCounts = {{1, "legal"}, {0, "banned"}};
|
||||
|
||||
SplitCardPart::SplitCardPart(const QString &_name,
|
||||
const QString &_text,
|
||||
const QVariantHash &_properties,
|
||||
const PrintingInfo &_printingInfo)
|
||||
: name(_name), text(_text), properties(_properties), printingInfo(_printingInfo)
|
||||
{
|
||||
}
|
||||
|
||||
const QRegularExpression OracleImporter::formatRegex = QRegularExpression("^format-");
|
||||
|
||||
OracleImporter::OracleImporter(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
static CardSet::Priority getSetPriority(const QString &setType, const QString &shortName)
|
||||
{
|
||||
if (!setTypePriorities.contains(setType.toLower())) {
|
||||
qDebug() << "warning: Set type" << setType << "unrecognized for prioritization";
|
||||
}
|
||||
CardSet::Priority priority = setTypePriorities.value(setType.toLower(), CardSet::PriorityOther);
|
||||
if (nonEnglishSets.contains(shortName)) {
|
||||
priority = CardSet::PriorityLowest;
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
|
||||
bool OracleImporter::readSetsFromByteArray(const QByteArray &data)
|
||||
{
|
||||
bool ok;
|
||||
auto setsMap = QtJson::Json::parse(QString(data), ok).toMap().value("data").toMap();
|
||||
if (!ok) {
|
||||
qDebug() << "error: QtJson::Json::parse()";
|
||||
return false;
|
||||
}
|
||||
|
||||
QList<SetToDownload> newSetList;
|
||||
|
||||
QListIterator it(setsMap.values());
|
||||
|
||||
while (it.hasNext()) {
|
||||
QVariantMap map = it.next().toMap();
|
||||
QString shortName = map.value("code").toString().toUpper();
|
||||
QString longName = map.value("name").toString();
|
||||
QList<QVariant> setCards = map.value("cards").toList();
|
||||
QString setType = map.value("type").toString();
|
||||
QDate releaseDate = map.value("releaseDate").toDate();
|
||||
CardSet::Priority priority = getSetPriority(setType, shortName);
|
||||
// capitalize set type
|
||||
if (setType.length() > 0) {
|
||||
// basic grammar for words that aren't capitalized, like in "From the Vault"
|
||||
const QStringList noCapitalize = {"the", "a", "an", "on", "to", "for", "of", "in", "and", "with", "or"};
|
||||
QStringList words = setType.split("_");
|
||||
setType.clear();
|
||||
bool first = false;
|
||||
for (auto &item : words) {
|
||||
if (first && noCapitalize.contains(item)) {
|
||||
setType += item + QString(" ");
|
||||
} else {
|
||||
setType += item[0].toUpper() + item.mid(1, -1) + QString(" ");
|
||||
first = true;
|
||||
}
|
||||
}
|
||||
setType = setType.trimmed();
|
||||
}
|
||||
newSetList.append(SetToDownload(shortName, longName, setCards, priority, setType, releaseDate));
|
||||
}
|
||||
|
||||
std::sort(newSetList.begin(), newSetList.end());
|
||||
|
||||
if (newSetList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
allSets = newSetList;
|
||||
return true;
|
||||
}
|
||||
|
||||
static QString getMainCardType(const QStringList &typeList)
|
||||
{
|
||||
if (typeList.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
static const QStringList typePriority = {"Planeswalker", "Creature", "Land", "Sorcery",
|
||||
"Instant", "Artifact", "Enchantment"};
|
||||
|
||||
for (const auto &type : typePriority) {
|
||||
if (typeList.contains(type)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
return typeList.first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts and deduplicates the color chars in the string by WUBRG order.
|
||||
*
|
||||
* @param colors The string containing the color chars. Will be modified in-place
|
||||
*/
|
||||
static void sortAndReduceColors(QString &colors)
|
||||
{
|
||||
// sort
|
||||
static const QHash<QChar, unsigned int> colorOrder{{'W', 0}, {'U', 1}, {'B', 2}, {'R', 3}, {'G', 4}};
|
||||
std::sort(colors.begin(), colors.end(),
|
||||
[](const QChar a, const QChar b) { return colorOrder.value(a, INT_MAX) < colorOrder.value(b, INT_MAX); });
|
||||
// reduce
|
||||
QChar lastChar = '\0';
|
||||
for (int i = 0; i < colors.size(); ++i) {
|
||||
if (colors.at(i) == lastChar) {
|
||||
colors.remove(i, 1);
|
||||
} else {
|
||||
lastChar = colors.at(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CardInfoPtr OracleImporter::addCard(QString name,
|
||||
const QString &text,
|
||||
bool isToken,
|
||||
QVariantHash properties,
|
||||
const QList<CardRelation *> &relatedCards,
|
||||
const PrintingInfo &printingInfo)
|
||||
{
|
||||
// Workaround for card name weirdness
|
||||
name = name.replace("Æ", "AE");
|
||||
name = name.replace("’", "'");
|
||||
if (cards.contains(name)) {
|
||||
CardInfoPtr card = cards.value(name);
|
||||
card->addToSet(printingInfo.getSet(), printingInfo);
|
||||
if (card->getProperties().filter(formatRegex).empty()) {
|
||||
card->combineLegalities(properties);
|
||||
}
|
||||
return card;
|
||||
}
|
||||
|
||||
// Remove {} around mana costs, except if it's split cost
|
||||
QString manacost = properties.value("manacost").toString();
|
||||
if (!manacost.isEmpty()) {
|
||||
QStringList symbols = manacost.split("}");
|
||||
QString formattedCardCost;
|
||||
for (QString symbol : symbols) {
|
||||
static const auto manaCostPattern = QRegularExpression("[0-9WUBGRP]/[0-9WUBGRP]");
|
||||
if (symbol.contains(manaCostPattern)) {
|
||||
symbol.append("}");
|
||||
} else {
|
||||
symbol.remove(QChar('{'));
|
||||
}
|
||||
formattedCardCost.append(symbol);
|
||||
}
|
||||
properties.insert("manacost", formattedCardCost);
|
||||
}
|
||||
|
||||
// fix colors
|
||||
QString allColors = properties.value("colors").toString();
|
||||
if (allColors.size() > 1) {
|
||||
sortAndReduceColors(allColors);
|
||||
properties.insert("colors", allColors);
|
||||
}
|
||||
QString allColorIdent = properties.value("coloridentity").toString();
|
||||
if (allColorIdent.size() > 1) {
|
||||
sortAndReduceColors(allColorIdent);
|
||||
properties.insert("coloridentity", allColorIdent);
|
||||
}
|
||||
|
||||
// DETECT CARD POSITIONING INFO
|
||||
|
||||
bool landscapeOrientation = properties.value("maintype").toString() == "Battle" ||
|
||||
properties.value("layout").toString() == "split" ||
|
||||
properties.value("layout").toString() == "planar";
|
||||
|
||||
// cards that enter the field tapped
|
||||
bool cipt = parseCipt(name, text) || landscapeOrientation;
|
||||
|
||||
// table row
|
||||
int tableRow = 1;
|
||||
QString mainCardType = properties.value("maintype").toString();
|
||||
if (mainCardType == "Land") {
|
||||
tableRow = 0;
|
||||
} else if (mainCardType == "Sorcery" || mainCardType == "Instant") {
|
||||
tableRow = 3;
|
||||
} else if (mainCardType == "Creature") {
|
||||
tableRow = 2;
|
||||
}
|
||||
|
||||
// card side
|
||||
QString side = properties.value("side").toString() == "b" ? "back" : "front";
|
||||
properties.insert("side", side);
|
||||
|
||||
// upsideDown (flip cards)
|
||||
QString layout = properties.value("layout").toString();
|
||||
bool upsideDown = layout == "flip" && side == "back";
|
||||
|
||||
// insert the card and its properties
|
||||
SetToPrintingsMap setsInfo;
|
||||
setsInfo[printingInfo.getSet()->getShortName()].append(printingInfo);
|
||||
CardInfo::UiAttributes attributes = {cipt, landscapeOrientation, tableRow, upsideDown};
|
||||
CardInfoPtr newCard =
|
||||
CardInfo::newInstance(name, text, isToken, properties, relatedCards, {}, setsInfo, attributes);
|
||||
|
||||
if (name.isEmpty()) {
|
||||
qDebug() << "warning: an empty card was added to set" << printingInfo.getSet()->getShortName();
|
||||
}
|
||||
cards.insert(name, newCard);
|
||||
|
||||
return newCard;
|
||||
}
|
||||
|
||||
static QString getStringPropertyFromMap(const QVariantMap &card, const QString &propertyName)
|
||||
{
|
||||
return card.contains(propertyName) ? card.value(propertyName).toString() : QString("");
|
||||
}
|
||||
|
||||
int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QList<QVariant> &cardsList)
|
||||
{
|
||||
// mtgjson name => xml name
|
||||
static const QMap<QString, QString> cardProperties{
|
||||
{"manaCost", "manacost"}, {"manaValue", "cmc"}, {"type", "type"},
|
||||
{"loyalty", "loyalty"}, {"layout", "layout"}, {"side", "side"},
|
||||
{"convertedManaCost", "cmc"}, // old name for manaValue, for backwards compatibility
|
||||
};
|
||||
|
||||
// mtgjson name => xml name
|
||||
static const QMap<QString, QString> setInfoProperties{
|
||||
{"number", "num"}, {"rarity", "rarity"}, {"isOnlineOnly", "isOnlineOnly"}, {"isRebalanced", "isRebalanced"}};
|
||||
|
||||
// mtgjson name => xml name
|
||||
static const QMap<QString, QString> identifierProperties{{"multiverseId", "muid"}, {"scryfallId", "uuid"}};
|
||||
|
||||
static const QString ptSeparator = "/";
|
||||
static constexpr bool isToken = false;
|
||||
static const QList<QString> setsWithCardsWithSameNameButDifferentText = {"UST"};
|
||||
|
||||
int numCards = 0;
|
||||
|
||||
// Keeps track of any split card faces encountered so far
|
||||
QMap<QString, QPair<QList<SplitCardPart>, QString>> splitCards;
|
||||
|
||||
// Keeps track of all names encountered so far
|
||||
QList<QString> allNameProps;
|
||||
|
||||
for (const QVariant &cardVar : cardsList) {
|
||||
QVariantMap card = cardVar.toMap();
|
||||
|
||||
/* Currently used layouts are:
|
||||
* augment, double_faced_token, flip, host, leveler, meld, normal, planar,
|
||||
* saga, scheme, split, token, transform, vanguard
|
||||
*/
|
||||
QString layout = getStringPropertyFromMap(card, "layout");
|
||||
|
||||
// don't import tokens from the json file
|
||||
if (layout == "token") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// normal cards handling
|
||||
QString name = getStringPropertyFromMap(card, "name");
|
||||
QString text = getStringPropertyFromMap(card, "text");
|
||||
QString faceName = getStringPropertyFromMap(card, "faceName");
|
||||
if (faceName.isEmpty()) {
|
||||
faceName = name;
|
||||
}
|
||||
|
||||
// card properties
|
||||
QVariantHash properties;
|
||||
for (auto i = cardProperties.cbegin(), end = cardProperties.cend(); i != end; ++i) {
|
||||
QString mtgjsonProperty = i.key();
|
||||
QString xmlPropertyName = i.value();
|
||||
QString propertyValue = getStringPropertyFromMap(card, mtgjsonProperty);
|
||||
if (!propertyValue.isEmpty()) {
|
||||
properties.insert(xmlPropertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
// per-set properties
|
||||
PrintingInfo printingInfo = PrintingInfo(currentSet);
|
||||
for (auto i = setInfoProperties.cbegin(), end = setInfoProperties.cend(); i != end; ++i) {
|
||||
QString mtgjsonProperty = i.key();
|
||||
QString xmlPropertyName = i.value();
|
||||
QString propertyValue = getStringPropertyFromMap(card, mtgjsonProperty);
|
||||
if (!propertyValue.isEmpty()) {
|
||||
printingInfo.setProperty(xmlPropertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
// handle flavorNames specially due to double-faced cards
|
||||
QString faceFlavorName = getStringPropertyFromMap(card, "faceFlavorName");
|
||||
QString flavorName = !faceFlavorName.isEmpty() ? faceFlavorName : getStringPropertyFromMap(card, "flavorName");
|
||||
if (!flavorName.isEmpty()) {
|
||||
printingInfo.setProperty("flavorName", flavorName);
|
||||
}
|
||||
|
||||
// Identifiers
|
||||
for (auto i = identifierProperties.cbegin(), end = identifierProperties.cend(); i != end; ++i) {
|
||||
QString mtgjsonProperty = i.key();
|
||||
QString xmlPropertyName = i.value();
|
||||
QString propertyValue = getStringPropertyFromMap(card.value("identifiers").toMap(), mtgjsonProperty);
|
||||
if (!propertyValue.isEmpty()) {
|
||||
printingInfo.setProperty(xmlPropertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
QString numComponent;
|
||||
const QString numProperty = printingInfo.getProperty("num");
|
||||
const QChar lastChar = numProperty.isEmpty() ? QChar() : numProperty.back();
|
||||
|
||||
// Un-Sets do some wonky stuff. Split up these cards as individual entries.
|
||||
// these cards will have a num with a letter (abc) behind it, put that letter into the name
|
||||
if (setsWithCardsWithSameNameButDifferentText.contains(currentSet->getShortName()) &&
|
||||
allNameProps.contains(faceName) && layout == "normal" && lastChar.isLetter()) {
|
||||
numComponent = " (" + QString(lastChar).toLower() + ")";
|
||||
}
|
||||
allNameProps.append(faceName);
|
||||
|
||||
// special handling properties
|
||||
QString colors = card.value("colors").toStringList().join("");
|
||||
if (!colors.isEmpty()) {
|
||||
properties.insert("colors", colors);
|
||||
}
|
||||
|
||||
// special handling properties
|
||||
QString colorIdentity = card.value("colorIdentity").toStringList().join("");
|
||||
if (!colorIdentity.isEmpty()) {
|
||||
properties.insert("coloridentity", colorIdentity);
|
||||
}
|
||||
|
||||
const auto &mainCardType = getMainCardType(card.value("types").toStringList());
|
||||
if (mainCardType.isEmpty()) {
|
||||
qDebug() << "warning: no mainCardType for card:" << name;
|
||||
} else {
|
||||
properties.insert("maintype", mainCardType);
|
||||
}
|
||||
|
||||
// Depending on whether power and/or toughness are present, the format
|
||||
// is either P/T (most common), P (no toughness), or /T (no power).
|
||||
QString power = getStringPropertyFromMap(card, "power");
|
||||
QString toughness = getStringPropertyFromMap(card, "toughness");
|
||||
if (toughness.isEmpty() && !power.isEmpty()) {
|
||||
properties.insert("pt", power);
|
||||
} else if (!toughness.isEmpty()) {
|
||||
properties.insert("pt", power + ptSeparator + toughness);
|
||||
}
|
||||
|
||||
auto legalities = card.value("legalities").toMap();
|
||||
for (auto i = legalities.cbegin(), end = legalities.cend(); i != end; ++i) {
|
||||
properties.insert(QString("format-%1").arg(i.key()), i.value().toString().toLower());
|
||||
}
|
||||
|
||||
// split cards are considered a single card, enqueue for later merging
|
||||
if (layout == "split" || layout == "aftermath" || layout == "adventure" || layout == "prepare") {
|
||||
auto _faceName = getStringPropertyFromMap(card, "faceName");
|
||||
SplitCardPart split(_faceName, text, properties, printingInfo);
|
||||
auto found_iter = splitCards.find(name + numProperty);
|
||||
if (found_iter == splitCards.end()) {
|
||||
splitCards.insert(name + numProperty, {{split}, name});
|
||||
} else {
|
||||
found_iter->first.append(split);
|
||||
}
|
||||
} else {
|
||||
// relations
|
||||
QList<CardRelation *> relatedCards;
|
||||
|
||||
// add other face for split cards as card relation
|
||||
if (!getStringPropertyFromMap(card, "side").isEmpty()) {
|
||||
auto faceManaValue = getStringPropertyFromMap(card, "faceManaValue");
|
||||
if (faceManaValue.isEmpty()) {
|
||||
// check the old name for the property, for backwards compatibility purposes
|
||||
faceManaValue = getStringPropertyFromMap(card, "faceConvertedManaCost");
|
||||
}
|
||||
properties["cmc"] = faceManaValue;
|
||||
|
||||
if (layout == "meld") { // meld cards don't work
|
||||
static const QRegularExpression meldNameRegex{"then meld them into ([^\\.]*)"};
|
||||
QString additionalName = meldNameRegex.match(text).captured(1);
|
||||
if (!additionalName.isNull()) {
|
||||
relatedCards.append(new CardRelation(additionalName, CardRelationType::TransformInto));
|
||||
}
|
||||
} else {
|
||||
for (const QString &additionalName : name.split(" // ")) {
|
||||
if (additionalName != faceName) {
|
||||
relatedCards.append(new CardRelation(additionalName, CardRelationType::TransformInto));
|
||||
}
|
||||
}
|
||||
}
|
||||
name = faceName;
|
||||
}
|
||||
|
||||
// mtgjon related cards
|
||||
if (card.contains("relatedCards")) {
|
||||
QVariantMap givenRelated = card.value("relatedCards").toMap();
|
||||
// conjured cards from a spellbook
|
||||
if (givenRelated.contains("spellbook")) {
|
||||
auto spbk = givenRelated.value("spellbook").toStringList();
|
||||
for (const QString &spbkName : spbk) {
|
||||
relatedCards.append(
|
||||
new CardRelation(spbkName, CardRelationType::DoesNotAttach, false, false, 1, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CardInfoPtr newCard = addCard(name + numComponent, text, isToken, properties, relatedCards, printingInfo);
|
||||
numCards++;
|
||||
}
|
||||
}
|
||||
|
||||
// split cards handling
|
||||
static const QString splitCardPropSeparator = QString(" // ");
|
||||
static const QString splitCardTextSeparator = QString("\n\n---\n\n");
|
||||
static const QList<CardRelation *> noRelatedCards = {};
|
||||
|
||||
QList<QPair<QList<SplitCardPart>, QString>> partsAndNames = splitCards.values();
|
||||
for (auto [splitCardParts, name] : partsAndNames) {
|
||||
QString text;
|
||||
QVariantHash properties;
|
||||
PrintingInfo printingInfo;
|
||||
|
||||
for (const SplitCardPart &tmp : splitCardParts) {
|
||||
if (!text.isEmpty()) {
|
||||
text.append(splitCardTextSeparator);
|
||||
}
|
||||
text.append(tmp.getText());
|
||||
|
||||
if (properties.isEmpty()) {
|
||||
properties = tmp.getProperties();
|
||||
printingInfo = tmp.getPrintingInfo();
|
||||
} else {
|
||||
const QVariantHash &tmpProps = tmp.getProperties();
|
||||
for (auto i = tmpProps.cbegin(), end = tmpProps.cend(); i != end; ++i) {
|
||||
QString prop = i.key();
|
||||
QString originalPropertyValue = properties.value(prop).toString();
|
||||
QString thisCardPropertyValue = i.value().toString();
|
||||
if (!thisCardPropertyValue.isEmpty() && originalPropertyValue != thisCardPropertyValue) {
|
||||
if (originalPropertyValue.isEmpty()) { // don't create //es if one field is empty
|
||||
properties.insert(prop, thisCardPropertyValue);
|
||||
} else if (prop == "colors") { // the card is both colors
|
||||
properties.insert(prop, originalPropertyValue + thisCardPropertyValue);
|
||||
} else if (prop == "maintype") { // don't create maintypes with //es in them
|
||||
continue;
|
||||
} else {
|
||||
properties.insert(prop,
|
||||
originalPropertyValue + splitCardPropSeparator + thisCardPropertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CardInfoPtr newCard = addCard(name, text, isToken, properties, noRelatedCards, printingInfo);
|
||||
numCards++;
|
||||
}
|
||||
|
||||
return numCards;
|
||||
}
|
||||
|
||||
FormatRulesNameMap OracleImporter::createDefaultMagicFormats()
|
||||
{
|
||||
// Predefined common exceptions
|
||||
CardCondition superTypeIsBasic;
|
||||
superTypeIsBasic.field = "type";
|
||||
superTypeIsBasic.matchType = "regex";
|
||||
superTypeIsBasic.value = "\bBasic\b[^—]+\bLand\b";
|
||||
|
||||
ExceptionRule basicLands;
|
||||
basicLands.conditions.append(superTypeIsBasic);
|
||||
|
||||
CardCondition anyNumberAllowed;
|
||||
anyNumberAllowed.field = "text";
|
||||
anyNumberAllowed.matchType = "contains";
|
||||
anyNumberAllowed.value = "A deck can have any number of";
|
||||
|
||||
ExceptionRule mayContainAnyNumber;
|
||||
mayContainAnyNumber.conditions.append(anyNumberAllowed);
|
||||
|
||||
// Map to store default rules
|
||||
FormatRulesNameMap defaultFormatRulesNameMap;
|
||||
|
||||
// ----------------- Helper lambda to create format -----------------
|
||||
auto makeFormat = [&](const QString &name, int minDeck = 60, int maxDeck = -1, int maxSideboardSize = 15,
|
||||
const QList<AllowedCount> &allowedCounts = kConstructedCounts) -> FormatRulesPtr {
|
||||
FormatRulesPtr f(new FormatRules);
|
||||
f->formatName = name;
|
||||
f->allowedCounts = allowedCounts;
|
||||
f->minDeckSize = minDeck;
|
||||
f->maxDeckSize = maxDeck;
|
||||
f->maxSideboardSize = maxSideboardSize;
|
||||
f->exceptions.append(basicLands);
|
||||
f->exceptions.append(mayContainAnyNumber);
|
||||
defaultFormatRulesNameMap.insert(name.toLower(), f);
|
||||
return f;
|
||||
};
|
||||
|
||||
// ----------------- Standard formats -----------------
|
||||
makeFormat("Standard");
|
||||
makeFormat("Modern");
|
||||
makeFormat("Legacy");
|
||||
makeFormat("Pioneer");
|
||||
makeFormat("Historic");
|
||||
makeFormat("Timeless");
|
||||
makeFormat("Future");
|
||||
makeFormat("OldSchool");
|
||||
makeFormat("Premodern");
|
||||
makeFormat("Pauper");
|
||||
makeFormat("Penny");
|
||||
|
||||
// ----------------- Singleton formats -----------------
|
||||
makeFormat("Commander", 100, 100, 15, kSingletonCounts);
|
||||
makeFormat("Duel", 100, 100, 15, kSingletonCounts);
|
||||
makeFormat("Brawl", 60, 60, 15, kSingletonCounts);
|
||||
makeFormat("StandardBrawl", 60, 60, 15, kSingletonCounts);
|
||||
makeFormat("Oathbreaker", 60, 60, 15, kSingletonCounts);
|
||||
makeFormat("PauperCommander", 100, 100, 15, kSingletonCounts);
|
||||
makeFormat("Predh", 100, 100, 15, kSingletonCounts);
|
||||
|
||||
// ----------------- Restricted formats -----------------
|
||||
makeFormat("Vintage", 60, -1, 15, {{4, "legal"}, {1, "restricted"}, {0, "banned"}});
|
||||
|
||||
return defaultFormatRulesNameMap;
|
||||
}
|
||||
|
||||
int OracleImporter::startImport()
|
||||
{
|
||||
static ICardSetPriorityController *noOpController = new NoopCardSetPriorityController();
|
||||
|
||||
// add an empty set for tokens
|
||||
CardSetPtr tokenSet =
|
||||
CardSet::newInstance(noOpController, CardSet::TOKENS_SETNAME, tr("Dummy set containing tokens"), "Tokens");
|
||||
sets.insert(CardSet::TOKENS_SETNAME, tokenSet);
|
||||
|
||||
int setIndex = 0;
|
||||
|
||||
for (const SetToDownload &curSetToParse : allSets) {
|
||||
CardSetPtr newSet = CardSet::newInstance(noOpController, curSetToParse.getShortName(),
|
||||
curSetToParse.getLongName(), curSetToParse.getSetType(),
|
||||
curSetToParse.getReleaseDate(), curSetToParse.getPriority());
|
||||
if (!sets.contains(newSet->getShortName())) {
|
||||
sets.insert(newSet->getShortName(), newSet);
|
||||
}
|
||||
|
||||
int numCardsInSet = importCardsFromSet(newSet, curSetToParse.getCards());
|
||||
|
||||
++setIndex;
|
||||
|
||||
emit setIndexChanged(numCardsInSet, setIndex, curSetToParse.getLongName());
|
||||
}
|
||||
|
||||
emit setIndexChanged(0, setIndex, QString());
|
||||
|
||||
// total number of sets
|
||||
return setIndex;
|
||||
}
|
||||
|
||||
bool OracleImporter::saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion)
|
||||
{
|
||||
CockatriceXml4Parser parser(new NoopCardPreferenceProvider(), new NoopCardSetPriorityController());
|
||||
|
||||
return parser.saveToFile(createDefaultMagicFormats(), sets, cards, fileName, sourceUrl, sourceVersion);
|
||||
}
|
||||
|
||||
void OracleImporter::clear()
|
||||
{
|
||||
sets.clear();
|
||||
cards.clear();
|
||||
allSets.clear();
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
#ifndef ORACLEIMPORTER_H
|
||||
#define ORACLEIMPORTER_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QRegularExpression>
|
||||
#include <QVariant>
|
||||
#include <libcockatrice/card/card_info.h>
|
||||
#include <utility>
|
||||
|
||||
// many users prefer not to see these sets with non english arts
|
||||
// they will given priority PriorityLowest
|
||||
const QStringList nonEnglishSets = {"4BB", "FBB", "PS11", "PSAL", "REN", "RIN"};
|
||||
const QMap<QString, CardSet::Priority> setTypePriorities{
|
||||
{"core", CardSet::PriorityPrimary},
|
||||
{"expansion", CardSet::PriorityPrimary},
|
||||
|
||||
{"commander", CardSet::PrioritySecondary},
|
||||
{"starter", CardSet::PrioritySecondary},
|
||||
{"draft_innovation", CardSet::PrioritySecondary},
|
||||
{"duel_deck", CardSet::PrioritySecondary},
|
||||
|
||||
{"archenemy", CardSet::PriorityReprint},
|
||||
{"arsenal", CardSet::PriorityReprint},
|
||||
{"box", CardSet::PriorityReprint},
|
||||
{"eternal", CardSet::PriorityReprint},
|
||||
{"from_the_vault", CardSet::PriorityReprint},
|
||||
{"masterpiece", CardSet::PriorityReprint},
|
||||
{"masters", CardSet::PriorityReprint},
|
||||
{"memorabilia", CardSet::PriorityReprint},
|
||||
{"planechase", CardSet::PriorityReprint},
|
||||
{"premium_deck", CardSet::PriorityReprint},
|
||||
{"promo", CardSet::PriorityReprint},
|
||||
{"spellbook", CardSet::PriorityReprint},
|
||||
{"token", CardSet::PriorityReprint},
|
||||
{"treasure_chest", CardSet::PriorityReprint},
|
||||
|
||||
{"alchemy", CardSet::PriorityOther},
|
||||
{"funny", CardSet::PriorityOther},
|
||||
{"minigame", CardSet::PriorityOther},
|
||||
{"vanguard", CardSet::PriorityOther},
|
||||
};
|
||||
|
||||
class SetToDownload
|
||||
{
|
||||
private:
|
||||
QString shortName, longName;
|
||||
QList<QVariant> cards;
|
||||
QDate releaseDate;
|
||||
QString setType;
|
||||
CardSet::Priority priority;
|
||||
|
||||
public:
|
||||
const QString &getShortName() const
|
||||
{
|
||||
return shortName;
|
||||
}
|
||||
const QString &getLongName() const
|
||||
{
|
||||
return longName;
|
||||
}
|
||||
const QList<QVariant> &getCards() const
|
||||
{
|
||||
return cards;
|
||||
}
|
||||
const QString &getSetType() const
|
||||
{
|
||||
return setType;
|
||||
}
|
||||
const QDate &getReleaseDate() const
|
||||
{
|
||||
return releaseDate;
|
||||
}
|
||||
CardSet::Priority getPriority() const
|
||||
{
|
||||
return priority;
|
||||
}
|
||||
SetToDownload(QString _shortName,
|
||||
QString _longName,
|
||||
QList<QVariant> _cards,
|
||||
CardSet::Priority _priority,
|
||||
QString _setType = QString(),
|
||||
const QDate &_releaseDate = QDate())
|
||||
: shortName(std::move(_shortName)), longName(std::move(_longName)), cards(std::move(_cards)),
|
||||
releaseDate(_releaseDate), setType(std::move(_setType)), priority(_priority)
|
||||
{
|
||||
}
|
||||
bool operator<(const SetToDownload &set) const
|
||||
{
|
||||
return longName.compare(set.longName, Qt::CaseInsensitive) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
class SplitCardPart
|
||||
{
|
||||
public:
|
||||
SplitCardPart(const QString &_name,
|
||||
const QString &_text,
|
||||
const QVariantHash &_properties,
|
||||
const PrintingInfo &_printingInfo);
|
||||
inline const QString &getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
inline const QString &getText() const
|
||||
{
|
||||
return text;
|
||||
}
|
||||
inline const QVariantHash &getProperties() const
|
||||
{
|
||||
return properties;
|
||||
}
|
||||
inline const PrintingInfo &getPrintingInfo() const
|
||||
{
|
||||
return printingInfo;
|
||||
}
|
||||
|
||||
private:
|
||||
QString name;
|
||||
QString text;
|
||||
QVariantHash properties;
|
||||
PrintingInfo printingInfo;
|
||||
};
|
||||
|
||||
class OracleImporter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
static const QRegularExpression formatRegex;
|
||||
|
||||
/**
|
||||
* The cards, indexed by name.
|
||||
*/
|
||||
CardNameMap cards;
|
||||
|
||||
/**
|
||||
* The sets, indexed by short name.
|
||||
*/
|
||||
SetNameMap sets;
|
||||
|
||||
QList<SetToDownload> allSets;
|
||||
|
||||
CardInfoPtr addCard(QString name,
|
||||
const QString &text,
|
||||
bool isToken,
|
||||
QVariantHash properties,
|
||||
const QList<CardRelation *> &relatedCards,
|
||||
const PrintingInfo &printingInfo);
|
||||
signals:
|
||||
void setIndexChanged(int cardsImported, int setIndex, const QString &setName);
|
||||
void dataReadProgress(int bytesRead, int totalBytes);
|
||||
|
||||
public:
|
||||
explicit OracleImporter(QObject *parent = nullptr);
|
||||
bool readSetsFromByteArray(const QByteArray &data);
|
||||
int startImport();
|
||||
bool saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion);
|
||||
int importCardsFromSet(const CardSetPtr ¤tSet, const QList<QVariant> &cardsList);
|
||||
FormatRulesNameMap createDefaultMagicFormats();
|
||||
const CardNameMap &getCardList() const
|
||||
{
|
||||
return cards;
|
||||
}
|
||||
QList<SetToDownload> &getSets()
|
||||
{
|
||||
return allSets;
|
||||
}
|
||||
void clear();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,139 @@
|
||||
#include "oraclewizard.h"
|
||||
|
||||
#include "client/settings/cache_settings.h"
|
||||
#include "main.h"
|
||||
#include "oracleimporter.h"
|
||||
#include "pages.h"
|
||||
#include "pagetemplates.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QFileDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QNetworkReply>
|
||||
#include <QPushButton>
|
||||
#include <QScrollBar>
|
||||
#include <QtConcurrent>
|
||||
#include <QtGui>
|
||||
|
||||
OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent)
|
||||
{
|
||||
// define a dummy context that will be used where needed
|
||||
QString dummy = QT_TRANSLATE_NOOP("i18n", "English");
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
setWizardStyle(QWizard::ModernStyle);
|
||||
#endif
|
||||
|
||||
QString oracleSettingsFile = SettingsCache::instance().getSettingsPath() + "oracle.ini";
|
||||
settings = new QSettings(oracleSettingsFile, QSettings::IniFormat, this);
|
||||
|
||||
// We moved the oracle-specific settings from global.ini to a separate oracle.ini after 2.10
|
||||
if (!QFile::exists(oracleSettingsFile)) {
|
||||
migrateOracleSettings();
|
||||
}
|
||||
|
||||
connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &OracleWizard::updateLanguage);
|
||||
|
||||
importer = new OracleImporter(this);
|
||||
|
||||
nam = new QNetworkAccessManager(this);
|
||||
|
||||
QList<OracleWizardPage *> pages;
|
||||
|
||||
if (!isSpoilersOnly) {
|
||||
pages << new IntroPage << new LoadSetsPage << new SaveSetsPage << new LoadTokensPage << new OutroPage;
|
||||
} else {
|
||||
pages << new LoadSpoilersPage << new OutroPage;
|
||||
}
|
||||
|
||||
for (OracleWizardPage *page : pages) {
|
||||
addPage(page);
|
||||
|
||||
// Connect background auto-advance
|
||||
connect(page, &OracleWizardPage::readyToContinue, this, [this]() {
|
||||
if (backgroundMode) {
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates the oracle-specific settings from global.ini to oracle.ini
|
||||
*/
|
||||
void OracleWizard::migrateOracleSettings()
|
||||
{
|
||||
QString filePath = SettingsCache::instance().getSettingsPath() + "global.ini";
|
||||
auto globalSettings = QSettings(filePath, QSettings::IniFormat, this);
|
||||
|
||||
auto tryMigrateValue = [this, &globalSettings](const QString &name) {
|
||||
QVariant variant = globalSettings.value(name);
|
||||
if (variant.isValid()) {
|
||||
settings->setValue(name, variant.toString());
|
||||
}
|
||||
};
|
||||
|
||||
tryMigrateValue("allsetsurl");
|
||||
tryMigrateValue("tokensurl");
|
||||
tryMigrateValue("spoilersurl");
|
||||
}
|
||||
|
||||
void OracleWizard::updateLanguage()
|
||||
{
|
||||
qApp->removeTranslator(translator);
|
||||
installNewTranslator();
|
||||
}
|
||||
|
||||
void OracleWizard::changeEvent(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
QDialog::changeEvent(event);
|
||||
}
|
||||
|
||||
void OracleWizard::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Oracle Importer"));
|
||||
for (int i = 0; i < pageIds().count(); i++) {
|
||||
dynamic_cast<OracleWizardPage *>(page(i))->retranslateUi();
|
||||
}
|
||||
}
|
||||
|
||||
void OracleWizard::accept()
|
||||
{
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void OracleWizard::enableButtons()
|
||||
{
|
||||
button(QWizard::NextButton)->setDisabled(false);
|
||||
button(QWizard::BackButton)->setDisabled(false);
|
||||
}
|
||||
|
||||
void OracleWizard::disableButtons()
|
||||
{
|
||||
button(QWizard::NextButton)->setDisabled(true);
|
||||
button(QWizard::BackButton)->setDisabled(true);
|
||||
}
|
||||
|
||||
bool OracleWizard::saveTokensToFile(const QString &fileName)
|
||||
{
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
qDebug() << "File open (w) failed for" << fileName;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.write(tokensData) == -1) {
|
||||
qDebug() << "File write (w) failed for" << fileName;
|
||||
return false;
|
||||
}
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#ifndef ORACLEWIZARD_H
|
||||
#define ORACLEWIZARD_H
|
||||
|
||||
#include <QWizard>
|
||||
#include <utility>
|
||||
|
||||
class QCheckBox;
|
||||
class QGroupBox;
|
||||
class QComboBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QRadioButton;
|
||||
class QProgressBar;
|
||||
class QNetworkAccessManager;
|
||||
class QTextEdit;
|
||||
class QVBoxLayout;
|
||||
class OracleImporter;
|
||||
class QSettings;
|
||||
|
||||
class OracleWizard : public QWizard
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OracleWizard(QWidget *parent = nullptr);
|
||||
void accept() override;
|
||||
void enableButtons();
|
||||
void disableButtons();
|
||||
void retranslateUi();
|
||||
void setTokensData(QByteArray _tokensData)
|
||||
{
|
||||
tokensData = std::move(_tokensData);
|
||||
}
|
||||
bool hasTokensData()
|
||||
{
|
||||
return !tokensData.isEmpty();
|
||||
}
|
||||
void setCardSourceUrl(const QString &sourceUrl)
|
||||
{
|
||||
cardSourceUrl = sourceUrl;
|
||||
}
|
||||
void setCardSourceVersion(const QString &sourceVersion)
|
||||
{
|
||||
cardSourceVersion = sourceVersion;
|
||||
}
|
||||
const QString &getCardSourceUrl() const
|
||||
{
|
||||
return cardSourceUrl;
|
||||
}
|
||||
const QString &getCardSourceVersion() const
|
||||
{
|
||||
return cardSourceVersion;
|
||||
}
|
||||
bool saveTokensToFile(const QString &fileName);
|
||||
|
||||
void runInBackground()
|
||||
{
|
||||
backgroundMode = true;
|
||||
hide();
|
||||
currentPage()->initializePage();
|
||||
}
|
||||
|
||||
public:
|
||||
OracleImporter *importer;
|
||||
QSettings *settings;
|
||||
QNetworkAccessManager *nam;
|
||||
bool downloadedPlainXml = false;
|
||||
QByteArray xmlData;
|
||||
bool backgroundMode = false;
|
||||
|
||||
private slots:
|
||||
void updateLanguage();
|
||||
|
||||
private:
|
||||
QByteArray tokensData;
|
||||
QString cardSourceUrl;
|
||||
QString cardSourceVersion;
|
||||
|
||||
void migrateOracleSettings();
|
||||
|
||||
protected:
|
||||
void changeEvent(QEvent *event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,742 @@
|
||||
#include "pages.h"
|
||||
|
||||
#include "client/settings/cache_settings.h"
|
||||
#include "main.h"
|
||||
#include "oracleimporter.h"
|
||||
#include "oraclewizard.h"
|
||||
#include "pages.h"
|
||||
#include "pagetemplates.h"
|
||||
#include "version_string.h"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QBuffer>
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QScrollBar>
|
||||
#include <QStandardPaths>
|
||||
#include <QTextEdit>
|
||||
#include <QtConcurrent>
|
||||
#include <QtGui>
|
||||
|
||||
#ifdef HAS_LZMA
|
||||
#include "lzma/decompress.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAS_ZLIB
|
||||
#include "zip/unzip.h"
|
||||
#endif
|
||||
|
||||
#define ZIP_SIGNATURE "PK"
|
||||
// Xz stream header: 0xFD + "7zXZ"
|
||||
#define XZ_SIGNATURE "\xFD\x37\x7A\x58\x5A"
|
||||
#define MTGJSON_V4_URL_COMPONENT "mtgjson.com/files/"
|
||||
#define ALLSETS_URL_FALLBACK "https://www.mtgjson.com/api/v5/AllPrintings.json"
|
||||
#define MTGJSON_VERSION_URL "https://www.mtgjson.com/api/v5/Meta.json"
|
||||
|
||||
#ifdef HAS_LZMA
|
||||
#define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json.xz"
|
||||
#elif defined(HAS_ZLIB)
|
||||
#define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json.zip"
|
||||
#else
|
||||
#define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json"
|
||||
#endif
|
||||
|
||||
#define TOKENS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Token/master/tokens.xml"
|
||||
#define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml"
|
||||
|
||||
IntroPage::IntroPage(QWidget *parent) : OracleWizardPage(parent)
|
||||
{
|
||||
label = new QLabel(this);
|
||||
label->setWordWrap(true);
|
||||
|
||||
languageLabel = new QLabel(this);
|
||||
versionLabel = new QLabel(this);
|
||||
languageBox = new QComboBox(this);
|
||||
|
||||
QStringList languageCodes = findQmFiles();
|
||||
for (const QString &code : languageCodes) {
|
||||
QString langName = languageName(code);
|
||||
languageBox->addItem(langName, code);
|
||||
}
|
||||
|
||||
QString setLanguage = QCoreApplication::translate("i18n", DEFAULT_LANG_NAME);
|
||||
int index = languageBox->findText(setLanguage, Qt::MatchExactly);
|
||||
if (index == -1) {
|
||||
qWarning() << "could not find language" << setLanguage;
|
||||
} else {
|
||||
languageBox->setCurrentIndex(index);
|
||||
}
|
||||
|
||||
connect(languageBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &IntroPage::languageBoxChanged);
|
||||
|
||||
auto *layout = new QGridLayout(this);
|
||||
layout->addWidget(label, 0, 0, 1, 2);
|
||||
layout->addWidget(languageLabel, 1, 0);
|
||||
layout->addWidget(languageBox, 1, 1);
|
||||
layout->addWidget(versionLabel, 2, 0, 1, 2);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void IntroPage::initializePage()
|
||||
{
|
||||
if (wizard()->backgroundMode) {
|
||||
emit readyToContinue();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList IntroPage::findQmFiles()
|
||||
{
|
||||
QDir dir(translationPath);
|
||||
QStringList fileNames = dir.entryList(QStringList(translationPrefix + "_*.qm"), QDir::Files, QDir::Name);
|
||||
fileNames.replaceInStrings(QRegularExpression(translationPrefix + "_(.*)\\.qm"), "\\1");
|
||||
return fileNames;
|
||||
}
|
||||
|
||||
QString IntroPage::languageName(const QString &lang)
|
||||
{
|
||||
QTranslator qTranslator;
|
||||
|
||||
QString appNameHint = translationPrefix + "_" + lang;
|
||||
bool appTranslationLoaded = qTranslator.load(appNameHint, translationPath);
|
||||
if (!appTranslationLoaded) {
|
||||
qDebug() << "Unable to load" << translationPrefix << "translation" << appNameHint << "at" << translationPath;
|
||||
}
|
||||
|
||||
return qTranslator.translate("i18n", DEFAULT_LANG_NAME);
|
||||
}
|
||||
|
||||
void IntroPage::languageBoxChanged(int index)
|
||||
{
|
||||
SettingsCache::instance().setLang(languageBox->itemData(index).toString());
|
||||
}
|
||||
|
||||
void IntroPage::retranslateUi()
|
||||
{
|
||||
setTitle(tr("Introduction"));
|
||||
label->setText(tr("This wizard will import the list of sets, cards, and tokens "
|
||||
"that will be used by Cockatrice."));
|
||||
languageLabel->setText(tr("Interface language:"));
|
||||
versionLabel->setText(tr("Version:") + QString(" %1").arg(VERSION_STRING));
|
||||
}
|
||||
|
||||
void OutroPage::retranslateUi()
|
||||
{
|
||||
setTitle(tr("Finished"));
|
||||
setSubTitle(tr("The wizard has finished.") + "<br>" +
|
||||
tr("You can now start using Cockatrice with the newly updated cards.") + "<br><br>" +
|
||||
tr("If the card databases don't reload automatically, restart the Cockatrice client."));
|
||||
}
|
||||
|
||||
void OutroPage::initializePage()
|
||||
{
|
||||
if (wizard()->backgroundMode) {
|
||||
wizard()->accept();
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
LoadSetsPage::LoadSetsPage(QWidget *parent) : OracleWizardPage(parent)
|
||||
{
|
||||
urlRadioButton = new QRadioButton(this);
|
||||
fileRadioButton = new QRadioButton(this);
|
||||
|
||||
urlLineEdit = new QLineEdit(this);
|
||||
fileLineEdit = new QLineEdit(this);
|
||||
|
||||
progressLabel = new QLabel(this);
|
||||
progressBar = new QProgressBar(this);
|
||||
|
||||
urlRadioButton->setChecked(true);
|
||||
|
||||
urlButton = new QPushButton(this);
|
||||
connect(urlButton, &QPushButton::clicked, this, &LoadSetsPage::actRestoreDefaultUrl);
|
||||
|
||||
fileButton = new QPushButton(this);
|
||||
connect(fileButton, &QPushButton::clicked, this, &LoadSetsPage::actLoadSetsFile);
|
||||
|
||||
auto *layout = new QGridLayout(this);
|
||||
layout->addWidget(urlRadioButton, 0, 0);
|
||||
layout->addWidget(urlLineEdit, 0, 1);
|
||||
layout->addWidget(urlButton, 1, 1, Qt::AlignRight);
|
||||
layout->addWidget(fileRadioButton, 2, 0);
|
||||
layout->addWidget(fileLineEdit, 2, 1);
|
||||
layout->addWidget(fileButton, 3, 1, Qt::AlignRight);
|
||||
layout->addWidget(progressLabel, 4, 0);
|
||||
layout->addWidget(progressBar, 4, 1);
|
||||
|
||||
connect(&watcher, &QFutureWatcher<bool>::finished, this, &LoadSetsPage::importFinished);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void LoadSetsPage::initializePage()
|
||||
{
|
||||
urlLineEdit->setText(wizard()->settings->value("allsetsurl", ALLSETS_URL).toString());
|
||||
|
||||
progressLabel->hide();
|
||||
progressBar->hide();
|
||||
|
||||
if (wizard()->backgroundMode) {
|
||||
if (isEnabled()) {
|
||||
validatePage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadSetsPage::retranslateUi()
|
||||
{
|
||||
setTitle(tr("Source selection"));
|
||||
setSubTitle(tr("Please specify a compatible source for the list of sets and cards. "
|
||||
"You can specify a URL address that will be downloaded or "
|
||||
"use an existing file from your computer."));
|
||||
|
||||
urlRadioButton->setText(tr("Download URL:"));
|
||||
fileRadioButton->setText(tr("Local file:"));
|
||||
urlButton->setText(tr("Restore default URL"));
|
||||
fileButton->setText(tr("Choose file..."));
|
||||
}
|
||||
|
||||
void LoadSetsPage::actRestoreDefaultUrl()
|
||||
{
|
||||
urlLineEdit->setText(ALLSETS_URL);
|
||||
}
|
||||
|
||||
void LoadSetsPage::actLoadSetsFile()
|
||||
{
|
||||
QFileDialog dialog(this, tr("Load sets file"));
|
||||
dialog.setFileMode(QFileDialog::ExistingFile);
|
||||
|
||||
QString extensions = "*.json *.xml";
|
||||
#ifdef HAS_ZLIB
|
||||
extensions += " *.zip";
|
||||
#endif
|
||||
#ifdef HAS_LZMA
|
||||
extensions += " *.xz";
|
||||
#endif
|
||||
dialog.setNameFilter(tr("Sets file (%1)").arg(extensions));
|
||||
|
||||
if (!fileLineEdit->text().isEmpty() && QFile::exists(fileLineEdit->text())) {
|
||||
dialog.selectFile(fileLineEdit->text());
|
||||
}
|
||||
|
||||
if (!dialog.exec()) {
|
||||
return;
|
||||
}
|
||||
|
||||
fileLineEdit->setText(dialog.selectedFiles().at(0));
|
||||
}
|
||||
|
||||
bool LoadSetsPage::validatePage()
|
||||
{
|
||||
// once the import is finished, we call next(); skip validation
|
||||
if (wizard()->downloadedPlainXml || wizard()->importer->getSets().count() > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// else, try to import sets
|
||||
if (urlRadioButton->isChecked()) {
|
||||
// If a user attempts to download from V4, redirect them to V5
|
||||
if (urlLineEdit->text().contains(MTGJSON_V4_URL_COMPONENT)) {
|
||||
actRestoreDefaultUrl();
|
||||
}
|
||||
|
||||
const auto url = QUrl::fromUserInput(urlLineEdit->text());
|
||||
|
||||
if (!url.isValid()) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("The provided URL is not valid."));
|
||||
return false;
|
||||
}
|
||||
|
||||
progressLabel->setText(tr("Downloading (0MB)"));
|
||||
// show an infinite progressbar
|
||||
progressBar->setMaximum(0);
|
||||
progressBar->setMinimum(0);
|
||||
progressBar->setValue(0);
|
||||
progressLabel->show();
|
||||
progressBar->show();
|
||||
|
||||
wizard()->disableButtons();
|
||||
setEnabled(false);
|
||||
|
||||
downloadSetsFile(url);
|
||||
} else if (fileRadioButton->isChecked()) {
|
||||
QFile setsFile(fileLineEdit->text());
|
||||
if (!setsFile.exists()) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Please choose a file."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!setsFile.open(QIODevice::ReadOnly)) {
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("Cannot open file '%1'.").arg(fileLineEdit->text()));
|
||||
return false;
|
||||
}
|
||||
|
||||
wizard()->disableButtons();
|
||||
setEnabled(false);
|
||||
|
||||
wizard()->setCardSourceUrl(setsFile.fileName());
|
||||
wizard()->setCardSourceVersion("unknown");
|
||||
|
||||
readSetsFromByteArray(setsFile.readAll());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void LoadSetsPage::downloadSetsFile(const QUrl &url)
|
||||
{
|
||||
wizard()->setCardSourceVersion("unknown");
|
||||
|
||||
const auto urlString = url.toString();
|
||||
if (urlString == ALLSETS_URL || urlString == ALLSETS_URL_FALLBACK) {
|
||||
const auto versionUrl = QUrl::fromUserInput(MTGJSON_VERSION_URL);
|
||||
QNetworkRequest request = QNetworkRequest(versionUrl);
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
auto *versionReply = wizard()->nam->get(request);
|
||||
connect(versionReply, &QNetworkReply::finished, [this, versionReply]() {
|
||||
if (versionReply->error() == QNetworkReply::NoError) {
|
||||
auto data = versionReply->readAll();
|
||||
QJsonParseError jsonError{};
|
||||
auto jsonResponse = QJsonDocument::fromJson(data, &jsonError);
|
||||
|
||||
if (jsonError.error == QJsonParseError::NoError) {
|
||||
const auto jsonMap = jsonResponse.toVariant().toMap();
|
||||
|
||||
auto versionString = jsonMap.value("meta").toMap().value("version").toString();
|
||||
if (versionString.isEmpty()) {
|
||||
versionString = "unknown";
|
||||
}
|
||||
wizard()->setCardSourceVersion(versionString);
|
||||
}
|
||||
}
|
||||
|
||||
versionReply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
wizard()->setCardSourceUrl(url.toString());
|
||||
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
|
||||
auto *reply = wizard()->nam->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, &LoadSetsPage::actDownloadFinishedSetsFile);
|
||||
connect(reply, &QNetworkReply::downloadProgress, this, &LoadSetsPage::actDownloadProgressSetsFile);
|
||||
}
|
||||
|
||||
void LoadSetsPage::actDownloadProgressSetsFile(qint64 received, qint64 total)
|
||||
{
|
||||
if (total > 0) {
|
||||
progressBar->setMaximum(static_cast<int>(total));
|
||||
progressBar->setValue(static_cast<int>(received));
|
||||
}
|
||||
progressLabel->setText(tr("Downloading (%1MB)").arg((int)received / (1024 * 1024)));
|
||||
}
|
||||
|
||||
void LoadSetsPage::actDownloadFinishedSetsFile()
|
||||
{
|
||||
// check for a reply
|
||||
auto *reply = dynamic_cast<QNetworkReply *>(sender());
|
||||
auto errorCode = reply->error();
|
||||
if (errorCode != QNetworkReply::NoError) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Network error: %1.").arg(reply->errorString()));
|
||||
|
||||
wizard()->enableButtons();
|
||||
setEnabled(true);
|
||||
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
auto statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (statusCode == 301 || statusCode == 302) {
|
||||
const auto redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
|
||||
qDebug() << "following redirect url:" << redirectUrl.toString();
|
||||
downloadSetsFile(redirectUrl);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
progressLabel->hide();
|
||||
progressBar->hide();
|
||||
|
||||
// save AllPrintings.json url, but only if the user customized it and download was successful
|
||||
if (urlLineEdit->text() != QString(ALLSETS_URL)) {
|
||||
wizard()->settings->setValue("allsetsurl", urlLineEdit->text());
|
||||
} else {
|
||||
wizard()->settings->remove("allsetsurl");
|
||||
}
|
||||
|
||||
readSetsFromByteArray(reply->readAll());
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void LoadSetsPage::readSetsFromByteArray(QByteArray _data)
|
||||
{
|
||||
// show an infinite progressbar
|
||||
progressBar->setMaximum(0);
|
||||
progressBar->setMinimum(0);
|
||||
progressBar->setValue(0);
|
||||
progressLabel->setText(tr("Parsing file"));
|
||||
progressLabel->show();
|
||||
progressBar->show();
|
||||
|
||||
wizard()->downloadedPlainXml = false;
|
||||
wizard()->xmlData.clear();
|
||||
readSetsFromByteArrayRef(_data);
|
||||
}
|
||||
|
||||
void LoadSetsPage::readSetsFromByteArrayRef(QByteArray &_data)
|
||||
{
|
||||
// unzip the file if needed
|
||||
if (_data.startsWith(XZ_SIGNATURE)) {
|
||||
#ifdef HAS_LZMA
|
||||
// zipped file
|
||||
auto *inBuffer = new QBuffer(&_data);
|
||||
auto newData = QByteArray();
|
||||
auto *outBuffer = new QBuffer(&newData);
|
||||
inBuffer->open(QBuffer::ReadOnly);
|
||||
outBuffer->open(QBuffer::WriteOnly);
|
||||
XzDecompressor xz;
|
||||
if (!xz.decompress(inBuffer, outBuffer)) {
|
||||
zipDownloadFailed(tr("Xz extraction failed."));
|
||||
return;
|
||||
}
|
||||
_data.clear();
|
||||
readSetsFromByteArrayRef(newData);
|
||||
return;
|
||||
#else
|
||||
zipDownloadFailed(tr("Sorry, this version of Oracle does not support xz compressed files."));
|
||||
|
||||
wizard()->enableButtons();
|
||||
setEnabled(true);
|
||||
progressLabel->hide();
|
||||
progressBar->hide();
|
||||
return;
|
||||
#endif
|
||||
} else if (_data.startsWith(ZIP_SIGNATURE)) {
|
||||
#ifdef HAS_ZLIB
|
||||
// zipped file
|
||||
auto *inBuffer = new QBuffer(&_data);
|
||||
auto newData = QByteArray();
|
||||
auto *outBuffer = new QBuffer(&newData);
|
||||
QString fileName;
|
||||
UnZip::ErrorCode ec;
|
||||
UnZip uz;
|
||||
|
||||
ec = uz.openArchive(inBuffer);
|
||||
if (ec != UnZip::Ok) {
|
||||
zipDownloadFailed(tr("Failed to open Zip archive: %1.").arg(uz.formatError(ec)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (uz.fileList().size() != 1) {
|
||||
zipDownloadFailed(tr("Zip extraction failed: the Zip archive doesn't contain exactly one file."));
|
||||
return;
|
||||
}
|
||||
fileName = uz.fileList().at(0);
|
||||
|
||||
outBuffer->open(QBuffer::ReadWrite);
|
||||
ec = uz.extractFile(fileName, outBuffer);
|
||||
if (ec != UnZip::Ok) {
|
||||
zipDownloadFailed(tr("Zip extraction failed: %1.").arg(uz.formatError(ec)));
|
||||
uz.closeArchive();
|
||||
return;
|
||||
}
|
||||
_data.clear();
|
||||
readSetsFromByteArrayRef(newData);
|
||||
return;
|
||||
#else
|
||||
zipDownloadFailed(tr("Sorry, this version of Oracle does not support zipped files."));
|
||||
|
||||
wizard()->enableButtons();
|
||||
setEnabled(true);
|
||||
progressLabel->hide();
|
||||
progressBar->hide();
|
||||
return;
|
||||
#endif
|
||||
} else if (_data.startsWith("{")) {
|
||||
// Start the computation.
|
||||
jsonData = std::move(_data);
|
||||
future = QtConcurrent::run([this] { return wizard()->importer->readSetsFromByteArray(std::move(jsonData)); });
|
||||
watcher.setFuture(future);
|
||||
} else if (_data.startsWith("<")) {
|
||||
// save xml file and don't do any processing
|
||||
wizard()->downloadedPlainXml = true;
|
||||
wizard()->xmlData = std::move(_data);
|
||||
importFinished();
|
||||
} else {
|
||||
wizard()->enableButtons();
|
||||
setEnabled(true);
|
||||
progressLabel->hide();
|
||||
progressBar->hide();
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to interpret downloaded data."));
|
||||
}
|
||||
}
|
||||
|
||||
void LoadSetsPage::zipDownloadFailed(const QString &message)
|
||||
{
|
||||
wizard()->enableButtons();
|
||||
setEnabled(true);
|
||||
progressLabel->hide();
|
||||
progressBar->hide();
|
||||
|
||||
QMessageBox::StandardButton reply;
|
||||
reply = static_cast<QMessageBox::StandardButton>(QMessageBox::question(
|
||||
this, tr("Error"), message + "<br>" + tr("Do you want to download the uncompressed file instead?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes));
|
||||
|
||||
if (reply == QMessageBox::Yes) {
|
||||
urlRadioButton->setChecked(true);
|
||||
urlLineEdit->setText(ALLSETS_URL_FALLBACK);
|
||||
|
||||
wizard()->next();
|
||||
}
|
||||
}
|
||||
|
||||
void LoadSetsPage::importFinished()
|
||||
{
|
||||
wizard()->enableButtons();
|
||||
setEnabled(true);
|
||||
progressLabel->hide();
|
||||
progressBar->hide();
|
||||
|
||||
if (wizard()->downloadedPlainXml || watcher.future().result()) {
|
||||
wizard()->next();
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("The file was retrieved successfully, but it does not contain any sets data."));
|
||||
}
|
||||
}
|
||||
|
||||
SaveSetsPage::SaveSetsPage(QWidget *parent) : OracleWizardPage(parent)
|
||||
{
|
||||
pathLabel = new QLabel(this);
|
||||
saveLabel = new QLabel(this);
|
||||
|
||||
defaultPathCheckBox = new QCheckBox(this);
|
||||
|
||||
messageLog = new QTextEdit(this);
|
||||
messageLog->setReadOnly(true);
|
||||
|
||||
auto *layout = new QGridLayout(this);
|
||||
layout->addWidget(messageLog, 0, 0);
|
||||
layout->addWidget(saveLabel, 1, 0);
|
||||
layout->addWidget(pathLabel, 2, 0);
|
||||
layout->addWidget(defaultPathCheckBox, 3, 0);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void SaveSetsPage::cleanupPage()
|
||||
{
|
||||
wizard()->importer->clear();
|
||||
disconnect(wizard()->importer, &OracleImporter::setIndexChanged, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void SaveSetsPage::initializePage()
|
||||
{
|
||||
messageLog->clear();
|
||||
|
||||
retranslateUi();
|
||||
if (wizard()->downloadedPlainXml) {
|
||||
messageLog->hide();
|
||||
} else {
|
||||
messageLog->show();
|
||||
connect(wizard()->importer, &OracleImporter::setIndexChanged, this, &SaveSetsPage::updateTotalProgress);
|
||||
|
||||
int setsImported = wizard()->importer->startImport();
|
||||
|
||||
if (setsImported == 0) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("No set has been imported."));
|
||||
}
|
||||
}
|
||||
|
||||
if (wizard()->backgroundMode) {
|
||||
emit readyToContinue();
|
||||
}
|
||||
}
|
||||
|
||||
void SaveSetsPage::retranslateUi()
|
||||
{
|
||||
setTitle(tr("Sets imported"));
|
||||
if (wizard()->downloadedPlainXml) {
|
||||
setSubTitle(tr("A cockatrice database file of %1 MB has been downloaded.")
|
||||
.arg(qRound(wizard()->xmlData.size() / 1000000.0)));
|
||||
} else {
|
||||
setSubTitle(tr("The following sets have been found:"));
|
||||
}
|
||||
|
||||
saveLabel->setText(tr("Press \"Save\" to store the imported cards in the Cockatrice database."));
|
||||
pathLabel->setText(tr("The card database will be saved at the following location:") + "<br>" +
|
||||
SettingsCache::instance().getCardDatabasePath());
|
||||
defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)"));
|
||||
|
||||
setButtonText(QWizard::NextButton, tr("&Save"));
|
||||
}
|
||||
|
||||
void SaveSetsPage::updateTotalProgress(int cardsImported, int /* setIndex */, const QString &setName)
|
||||
{
|
||||
if (setName.isEmpty()) {
|
||||
messageLog->append("<b>" + tr("Import finished: %1 cards.").arg(wizard()->importer->getCardList().size()) +
|
||||
"</b>");
|
||||
} else {
|
||||
messageLog->append(tr("%1: %2 cards imported").arg(setName).arg(cardsImported));
|
||||
}
|
||||
|
||||
messageLog->verticalScrollBar()->setValue(messageLog->verticalScrollBar()->maximum());
|
||||
}
|
||||
|
||||
bool SaveSetsPage::validatePage()
|
||||
{
|
||||
QString defaultPath = SettingsCache::instance().getCardDatabasePath();
|
||||
QString windowName = tr("Save card database");
|
||||
QString fileType = tr("XML; card database (*.xml)");
|
||||
|
||||
QString fileName;
|
||||
if (defaultPathCheckBox->isChecked()) {
|
||||
fileName = QFileDialog::getSaveFileName(this, windowName, defaultPath, fileType);
|
||||
} else {
|
||||
fileName = defaultPath;
|
||||
}
|
||||
|
||||
if (fileName.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QFileInfo fi(fileName);
|
||||
QDir fileDir(fi.path());
|
||||
if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wizard()->downloadedPlainXml) {
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
qDebug() << "File write (w) failed for" << fileName;
|
||||
return false;
|
||||
}
|
||||
if (file.write(wizard()->xmlData) < 1) {
|
||||
qDebug() << "File write (w) failed for" << fileName;
|
||||
return false;
|
||||
}
|
||||
wizard()->xmlData.clear();
|
||||
} else if (!wizard()->importer->saveToFile(fileName, wizard()->getCardSourceUrl(),
|
||||
wizard()->getCardSourceVersion())) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("The file could not be saved to %1").arg(fileName));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LoadTokensPage::initializePage()
|
||||
{
|
||||
SimpleDownloadFilePage::initializePage();
|
||||
|
||||
if (wizard()->backgroundMode) {
|
||||
emit readyToContinue();
|
||||
}
|
||||
}
|
||||
|
||||
QString LoadTokensPage::getDefaultUrl()
|
||||
{
|
||||
return TOKENS_URL;
|
||||
}
|
||||
|
||||
QString LoadTokensPage::getCustomUrlSettingsKey()
|
||||
{
|
||||
return "tokensurl";
|
||||
}
|
||||
|
||||
QString LoadTokensPage::getDefaultSavePath()
|
||||
{
|
||||
return SettingsCache::instance().getTokenDatabasePath();
|
||||
}
|
||||
|
||||
QString LoadTokensPage::getWindowTitle()
|
||||
{
|
||||
return tr("Save token database");
|
||||
}
|
||||
|
||||
QString LoadTokensPage::getFileType()
|
||||
{
|
||||
return tr("XML; token database (*.xml)");
|
||||
}
|
||||
|
||||
QString LoadTokensPage::getFilePromptName()
|
||||
{
|
||||
return tr("tokens");
|
||||
}
|
||||
|
||||
void LoadTokensPage::retranslateUi()
|
||||
{
|
||||
setTitle(tr("Tokens import"));
|
||||
setSubTitle(tr("Please specify a compatible source for token data."));
|
||||
|
||||
urlRadioButton->setText(tr("Download URL:"));
|
||||
fileRadioButton->setText(tr("Local file:"));
|
||||
urlButton->setText(tr("Restore default URL"));
|
||||
fileButton->setText(tr("Choose file..."));
|
||||
|
||||
pathLabel->setText(tr("The token database will be saved at the following location:") + "<br>" +
|
||||
SettingsCache::instance().getTokenDatabasePath());
|
||||
defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)"));
|
||||
}
|
||||
|
||||
QString LoadSpoilersPage::getDefaultUrl()
|
||||
{
|
||||
return SPOILERS_URL;
|
||||
}
|
||||
|
||||
QString LoadSpoilersPage::getCustomUrlSettingsKey()
|
||||
{
|
||||
return "spoilersurl";
|
||||
}
|
||||
|
||||
QString LoadSpoilersPage::getDefaultSavePath()
|
||||
{
|
||||
return SettingsCache::instance().getTokenDatabasePath();
|
||||
}
|
||||
|
||||
QString LoadSpoilersPage::getWindowTitle()
|
||||
{
|
||||
return tr("Save spoiler database");
|
||||
}
|
||||
|
||||
QString LoadSpoilersPage::getFileType()
|
||||
{
|
||||
return tr("XML; spoiler database (*.xml)");
|
||||
}
|
||||
|
||||
QString LoadSpoilersPage::getFilePromptName()
|
||||
{
|
||||
return tr("spoiler");
|
||||
}
|
||||
|
||||
void LoadSpoilersPage::retranslateUi()
|
||||
{
|
||||
setTitle(tr("Spoilers import"));
|
||||
setSubTitle(tr("Please specify a compatible source for spoiler data."));
|
||||
|
||||
urlRadioButton->setText(tr("Download URL:"));
|
||||
fileRadioButton->setText(tr("Local file:"));
|
||||
urlButton->setText(tr("Restore default URL"));
|
||||
fileButton->setText(tr("Choose file..."));
|
||||
|
||||
pathLabel->setText(tr("The spoiler database will be saved at the following location:") + "<br>" +
|
||||
SettingsCache::instance().getSpoilerCardDatabasePath());
|
||||
defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)"));
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
#ifndef COCKATRICE_PAGES_H
|
||||
#define COCKATRICE_PAGES_H
|
||||
|
||||
#include "pagetemplates.h"
|
||||
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QTimer>
|
||||
#include <QWizard>
|
||||
#include <utility>
|
||||
|
||||
class QCheckBox;
|
||||
class QGroupBox;
|
||||
class QComboBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QRadioButton;
|
||||
class QProgressBar;
|
||||
class QNetworkAccessManager;
|
||||
class QTextEdit;
|
||||
class QVBoxLayout;
|
||||
class OracleImporter;
|
||||
class QSettings;
|
||||
|
||||
class IntroPage : public OracleWizardPage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit IntroPage(QWidget *parent = nullptr);
|
||||
void retranslateUi() override;
|
||||
|
||||
private:
|
||||
QStringList findQmFiles();
|
||||
QString languageName(const QString &lang);
|
||||
|
||||
private:
|
||||
QLabel *label, *languageLabel, *versionLabel;
|
||||
QComboBox *languageBox;
|
||||
|
||||
private slots:
|
||||
void languageBoxChanged(int index);
|
||||
|
||||
protected slots:
|
||||
void initializePage() override;
|
||||
};
|
||||
|
||||
class OutroPage : public OracleWizardPage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OutroPage(QWidget * = nullptr)
|
||||
{
|
||||
}
|
||||
void retranslateUi() override;
|
||||
|
||||
protected:
|
||||
void initializePage() override;
|
||||
};
|
||||
|
||||
class LoadSetsPage : public OracleWizardPage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LoadSetsPage(QWidget *parent = nullptr);
|
||||
void retranslateUi() override;
|
||||
|
||||
protected:
|
||||
void initializePage() override;
|
||||
bool validatePage() override;
|
||||
void readSetsFromByteArray(QByteArray _data);
|
||||
void readSetsFromByteArrayRef(QByteArray &_data);
|
||||
void downloadSetsFile(const QUrl &url);
|
||||
|
||||
private:
|
||||
QRadioButton *urlRadioButton;
|
||||
QRadioButton *fileRadioButton;
|
||||
QLineEdit *urlLineEdit;
|
||||
QLineEdit *fileLineEdit;
|
||||
QPushButton *urlButton;
|
||||
QPushButton *fileButton;
|
||||
QLabel *progressLabel;
|
||||
QProgressBar *progressBar;
|
||||
|
||||
QFutureWatcher<bool> watcher;
|
||||
QFuture<bool> future;
|
||||
QByteArray jsonData;
|
||||
|
||||
private slots:
|
||||
void actLoadSetsFile();
|
||||
void actRestoreDefaultUrl();
|
||||
void actDownloadProgressSetsFile(qint64 received, qint64 total);
|
||||
void actDownloadFinishedSetsFile();
|
||||
void importFinished();
|
||||
void zipDownloadFailed(const QString &message);
|
||||
};
|
||||
|
||||
class SaveSetsPage : public OracleWizardPage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SaveSetsPage(QWidget *parent = nullptr);
|
||||
void retranslateUi() override;
|
||||
|
||||
private:
|
||||
QTextEdit *messageLog;
|
||||
QCheckBox *defaultPathCheckBox;
|
||||
QLabel *pathLabel;
|
||||
QLabel *saveLabel;
|
||||
|
||||
protected:
|
||||
void initializePage() override;
|
||||
void cleanupPage() override;
|
||||
bool validatePage() override;
|
||||
|
||||
private slots:
|
||||
void updateTotalProgress(int cardsImported, int setIndex, const QString &setName);
|
||||
};
|
||||
|
||||
class LoadSpoilersPage : public SimpleDownloadFilePage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LoadSpoilersPage(QWidget * = nullptr)
|
||||
{
|
||||
}
|
||||
void retranslateUi() override;
|
||||
|
||||
protected:
|
||||
QString getDefaultUrl() override;
|
||||
QString getCustomUrlSettingsKey() override;
|
||||
QString getDefaultSavePath() override;
|
||||
QString getWindowTitle() override;
|
||||
QString getFileType() override;
|
||||
QString getFilePromptName() override;
|
||||
};
|
||||
|
||||
class LoadTokensPage : public SimpleDownloadFilePage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LoadTokensPage(QWidget * = nullptr)
|
||||
{
|
||||
}
|
||||
void retranslateUi() override;
|
||||
|
||||
protected:
|
||||
QString getDefaultUrl() override;
|
||||
QString getCustomUrlSettingsKey() override;
|
||||
QString getDefaultSavePath() override;
|
||||
QString getWindowTitle() override;
|
||||
QString getFileType() override;
|
||||
QString getFilePromptName() override;
|
||||
void initializePage() override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_PAGES_H
|
||||
@@ -0,0 +1,248 @@
|
||||
#include "pagetemplates.h"
|
||||
|
||||
#include "oraclewizard.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QtGui>
|
||||
|
||||
SimpleDownloadFilePage::SimpleDownloadFilePage(QWidget *parent) : OracleWizardPage(parent)
|
||||
{
|
||||
urlRadioButton = new QRadioButton(this);
|
||||
fileRadioButton = new QRadioButton(this);
|
||||
|
||||
urlLineEdit = new QLineEdit(this);
|
||||
fileLineEdit = new QLineEdit(this);
|
||||
|
||||
progressLabel = new QLabel(this);
|
||||
progressBar = new QProgressBar(this);
|
||||
|
||||
urlRadioButton->setChecked(true);
|
||||
|
||||
urlButton = new QPushButton(this);
|
||||
connect(urlButton, &QPushButton::clicked, this, &SimpleDownloadFilePage::actRestoreDefaultUrl);
|
||||
|
||||
fileButton = new QPushButton(this);
|
||||
connect(fileButton, &QPushButton::clicked, this, &SimpleDownloadFilePage::actLoadCardFile);
|
||||
|
||||
defaultPathCheckBox = new QCheckBox(this);
|
||||
pathLabel = new QLabel(this);
|
||||
pathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
|
||||
auto *layout = new QGridLayout(this);
|
||||
layout->addWidget(urlRadioButton, 0, 0);
|
||||
layout->addWidget(urlLineEdit, 0, 1);
|
||||
layout->addWidget(urlButton, 1, 1, Qt::AlignRight);
|
||||
layout->addWidget(fileRadioButton, 2, 0);
|
||||
layout->addWidget(fileLineEdit, 2, 1);
|
||||
layout->addWidget(fileButton, 3, 1, Qt::AlignRight);
|
||||
layout->addWidget(pathLabel, 4, 0, 1, 2);
|
||||
layout->addWidget(defaultPathCheckBox, 5, 0, 1, 2);
|
||||
layout->addWidget(progressLabel, 6, 0);
|
||||
layout->addWidget(progressBar, 6, 1);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void SimpleDownloadFilePage::initializePage()
|
||||
{
|
||||
// get custom url from settings if any; otherwise use default url
|
||||
urlLineEdit->setText(wizard()->settings->value(getCustomUrlSettingsKey(), getDefaultUrl()).toString());
|
||||
|
||||
progressLabel->hide();
|
||||
progressBar->hide();
|
||||
}
|
||||
|
||||
void SimpleDownloadFilePage::actRestoreDefaultUrl()
|
||||
{
|
||||
urlLineEdit->setText(getDefaultUrl());
|
||||
}
|
||||
|
||||
void SimpleDownloadFilePage::actLoadCardFile()
|
||||
{
|
||||
QFileDialog dialog(this, tr("Load %1 file").arg(getFilePromptName()));
|
||||
dialog.setFileMode(QFileDialog::ExistingFile);
|
||||
|
||||
QString extensions = "*.json *.xml";
|
||||
#ifdef HAS_ZLIB
|
||||
extensions += " *.zip";
|
||||
#endif
|
||||
#ifdef HAS_LZMA
|
||||
extensions += " *.xz";
|
||||
#endif
|
||||
dialog.setNameFilter(tr("%1 file (%1)").arg(getFilePromptName(), extensions));
|
||||
|
||||
if (!fileLineEdit->text().isEmpty() && QFile::exists(fileLineEdit->text())) {
|
||||
dialog.selectFile(fileLineEdit->text());
|
||||
}
|
||||
|
||||
if (!dialog.exec()) {
|
||||
return;
|
||||
}
|
||||
|
||||
fileLineEdit->setText(dialog.selectedFiles().at(0));
|
||||
}
|
||||
|
||||
bool SimpleDownloadFilePage::validatePage()
|
||||
{
|
||||
// if data has already been downloaded, pass directly to the "save" step
|
||||
if (!downloadData.isEmpty()) {
|
||||
if (saveToFile()) {
|
||||
return true;
|
||||
} else {
|
||||
wizard()->enableButtons();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// else, try to import sets
|
||||
if (urlRadioButton->isChecked()) {
|
||||
QUrl url = QUrl::fromUserInput(urlLineEdit->text());
|
||||
if (!url.isValid()) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("The provided URL is not valid: ") + url.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
progressLabel->setText(tr("Downloading (0MB)"));
|
||||
// show an infinite progressbar
|
||||
progressBar->setMaximum(0);
|
||||
progressBar->setMinimum(0);
|
||||
progressBar->setValue(0);
|
||||
progressLabel->show();
|
||||
progressBar->show();
|
||||
|
||||
wizard()->disableButtons();
|
||||
downloadFile(url);
|
||||
|
||||
} else if (fileRadioButton->isChecked()) {
|
||||
QFile cardFile(fileLineEdit->text());
|
||||
if (!cardFile.exists()) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Please choose a file."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!cardFile.open(QIODevice::ReadOnly)) {
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("Cannot open file '%1'.").arg(fileLineEdit->text()));
|
||||
return false;
|
||||
}
|
||||
|
||||
downloadData = cardFile.readAll();
|
||||
wizard()->next();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SimpleDownloadFilePage::downloadFile(QUrl url)
|
||||
{
|
||||
QNetworkReply *reply = wizard()->nam->get(QNetworkRequest(url));
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, &SimpleDownloadFilePage::actDownloadFinished);
|
||||
connect(reply, &QNetworkReply::downloadProgress, this, &SimpleDownloadFilePage::actDownloadProgress);
|
||||
}
|
||||
|
||||
void SimpleDownloadFilePage::actDownloadProgress(qint64 received, qint64 total)
|
||||
{
|
||||
if (total > 0) {
|
||||
progressBar->setMaximum(static_cast<int>(total));
|
||||
progressBar->setValue(static_cast<int>(received));
|
||||
}
|
||||
progressLabel->setText(tr("Downloading (%1MB)").arg((int)received / (1024 * 1024)));
|
||||
}
|
||||
|
||||
void SimpleDownloadFilePage::actDownloadFinished()
|
||||
{
|
||||
// check for a reply
|
||||
auto *reply = dynamic_cast<QNetworkReply *>(sender());
|
||||
QNetworkReply::NetworkError errorCode = reply->error();
|
||||
if (errorCode != QNetworkReply::NoError) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Network error: %1.").arg(reply->errorString()));
|
||||
wizard()->enableButtons();
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (statusCode == 301 || statusCode == 302) {
|
||||
QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
|
||||
qDebug() << "following redirect url:" << redirectUrl.toString();
|
||||
downloadFile(redirectUrl);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// save downloaded file url, but only if the user customized it and download was successful
|
||||
if (urlLineEdit->text() != getDefaultUrl()) {
|
||||
wizard()->settings->setValue(getCustomUrlSettingsKey(), urlLineEdit->text());
|
||||
} else {
|
||||
wizard()->settings->remove(getCustomUrlSettingsKey());
|
||||
}
|
||||
|
||||
downloadData = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
wizard()->enableButtons();
|
||||
progressLabel->hide();
|
||||
progressBar->hide();
|
||||
|
||||
wizard()->next();
|
||||
}
|
||||
|
||||
bool SimpleDownloadFilePage::saveToFile()
|
||||
{
|
||||
QString defaultPath = getDefaultSavePath();
|
||||
QString windowName = getWindowTitle();
|
||||
QString fileType = getFileType();
|
||||
|
||||
QString fileName;
|
||||
if (defaultPathCheckBox->isChecked()) {
|
||||
fileName = QFileDialog::getSaveFileName(this, windowName, defaultPath, fileType);
|
||||
} else {
|
||||
fileName = defaultPath;
|
||||
}
|
||||
|
||||
if (fileName.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QFileInfo fi(fileName);
|
||||
QDir fileDir(fi.path());
|
||||
if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!internalSaveToFile(fileName)) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("The file could not be saved to %1").arg(fileName));
|
||||
return false;
|
||||
}
|
||||
|
||||
// clean saved downloadData
|
||||
downloadData = QByteArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SimpleDownloadFilePage::internalSaveToFile(const QString &fileName)
|
||||
{
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
qDebug() << "File open (w) failed for" << fileName;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.write(downloadData) == -1) {
|
||||
qDebug() << "File write (w) failed for" << fileName;
|
||||
return false;
|
||||
}
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#ifndef PAGETEMPLATES_H
|
||||
#define PAGETEMPLATES_H
|
||||
|
||||
#include <QWizardPage>
|
||||
|
||||
class QFile;
|
||||
class QRadioButton;
|
||||
class OracleWizard;
|
||||
class QCheckBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QProgressBar;
|
||||
|
||||
class OracleWizardPage : public QWizardPage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OracleWizardPage(QWidget *parent = nullptr) : QWizardPage(parent)
|
||||
{
|
||||
}
|
||||
virtual void retranslateUi() = 0;
|
||||
|
||||
signals:
|
||||
void readyToContinue();
|
||||
|
||||
protected:
|
||||
inline OracleWizard *wizard()
|
||||
{
|
||||
return (OracleWizard *)QWizardPage::wizard();
|
||||
}
|
||||
};
|
||||
|
||||
class SimpleDownloadFilePage : public OracleWizardPage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SimpleDownloadFilePage(QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void initializePage() override;
|
||||
bool validatePage() override;
|
||||
void downloadFile(QUrl url);
|
||||
virtual QString getDefaultUrl() = 0;
|
||||
virtual QString getCustomUrlSettingsKey() = 0;
|
||||
virtual QString getDefaultSavePath() = 0;
|
||||
virtual QString getWindowTitle() = 0;
|
||||
virtual QString getFileType() = 0;
|
||||
virtual QString getFilePromptName() = 0;
|
||||
bool saveToFile();
|
||||
bool internalSaveToFile(const QString &fileName);
|
||||
|
||||
protected:
|
||||
QByteArray downloadData;
|
||||
QRadioButton *urlRadioButton;
|
||||
QRadioButton *fileRadioButton;
|
||||
QLineEdit *urlLineEdit;
|
||||
QLineEdit *fileLineEdit;
|
||||
QPushButton *urlButton;
|
||||
QPushButton *fileButton;
|
||||
QLabel *pathLabel;
|
||||
QLabel *progressLabel;
|
||||
QProgressBar *progressBar;
|
||||
QCheckBox *defaultPathCheckBox;
|
||||
|
||||
signals:
|
||||
void parsedDataReady();
|
||||
private slots:
|
||||
void actRestoreDefaultUrl();
|
||||
void actLoadCardFile();
|
||||
void actDownloadProgress(qint64 received, qint64 total);
|
||||
void actDownloadFinished();
|
||||
};
|
||||
|
||||
#endif // PAGETEMPLATES_H
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "parsehelpers.h"
|
||||
|
||||
#include <QChar>
|
||||
#include <QRegularExpression>
|
||||
|
||||
/**
|
||||
* Parses the card text to determine if the card should have the cipt tag
|
||||
*
|
||||
* The parsing logic is able to handle the following cases:
|
||||
* - "<name> enters tapped"
|
||||
* - "<shortname> enters tapped", if the card name starts with the shortname
|
||||
* - "This <type> enters tapped"
|
||||
* - "..., it enters tapped"
|
||||
* - Any naming scheme that appends a non-alphanumeric character plus extra text to the end of the name.
|
||||
* (e.g. name is "Card Name_SET" or "Card Name (Set)" and text contains "Card Name enters tapped")
|
||||
*
|
||||
* However, it will still miss on certain cases:
|
||||
* - shortnames that aren't the at the beginning of the card name
|
||||
*
|
||||
* Note that "...enters tapped unless..." returns false.
|
||||
*
|
||||
* @param name The name of the card
|
||||
* @param text The oracle text of the card
|
||||
*/
|
||||
bool parseCipt(const QString &name, const QString &text)
|
||||
{
|
||||
// Use precompiled regex to check if text is a possible candidate, and early return if not
|
||||
static auto prelimCheck = QRegularExpression(" enters( the battlefield)? tapped(?! unless)");
|
||||
if (!prelimCheck.match(text).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to split shortname on most non-alphanumeric characters (including _)
|
||||
auto isShortnameDivider = [](const QChar &c) {
|
||||
return c == '_' || (!c.isLetterOrNumber() && c != '\'' && c != '\"');
|
||||
};
|
||||
|
||||
// Try all possible shortnames.
|
||||
// This also handles the case of extra text appended at end.
|
||||
QStringList possibleNames;
|
||||
bool inAlphanumericPart = true;
|
||||
for (int i = 0; i < name.length(); ++i) {
|
||||
if (isShortnameDivider(name.at(i))) {
|
||||
if (inAlphanumericPart) {
|
||||
// only add to names on a "falling edge", in order to reduce the amount of redundant splits
|
||||
possibleNames.append(QRegularExpression::escape(name.left(i)));
|
||||
inAlphanumericPart = false;
|
||||
}
|
||||
} else {
|
||||
inAlphanumericPart = true;
|
||||
}
|
||||
}
|
||||
|
||||
// and the full name
|
||||
possibleNames.append(QRegularExpression::escape(name));
|
||||
|
||||
QString subject = "(it|" // "..., it enters tapped"
|
||||
"(T|t)his [^ ]+|" // "This <type> enters tapped"
|
||||
+ possibleNames.join("|") + ")";
|
||||
|
||||
auto ciptPattern = QRegularExpression(
|
||||
// cipt phrase is either first sentence of line, or is after a punctuation mark
|
||||
"(^|(, |\\. ))" + subject +
|
||||
// support old wording, and exclude the "unless" case
|
||||
" enters( the battlefield)? tapped(?! unless)",
|
||||
QRegularExpression::MultilineOption);
|
||||
|
||||
return ciptPattern.match(text).hasMatch();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#ifndef PARSEHELPERS_H
|
||||
#define PARSEHELPERS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
bool parseCipt(const QString &name, const QString &text);
|
||||
|
||||
#endif // PARSEHELPERS_H
|
||||
@@ -0,0 +1,3 @@
|
||||
Eeli Reilin <eeli@emicode.fi>
|
||||
Luis Gustavo S. Barreto <gustavosbarreto@gmail.com>
|
||||
Stephen Kockentiedt <Stephen@Kockentiedt.name>
|
||||
@@ -0,0 +1,27 @@
|
||||
Copyright 2011 Eeli Reilin. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation
|
||||
are those of the authors and should not be interpreted as representing
|
||||
official policies, either expressed or implied, of Eeli Reilin.
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
########################################################################
|
||||
1. INTRODUCTION
|
||||
|
||||
The Json class is a simple class for parsing JSON data into a QVariant
|
||||
hierarchies. Now, we can also reverse the process and serialize
|
||||
QVariant hierarchies into valid JSON data.
|
||||
|
||||
|
||||
########################################################################
|
||||
2. HOW TO USE
|
||||
|
||||
The parser is really easy to use. Let's say we have the following
|
||||
QString of JSON data:
|
||||
|
||||
------------------------------------------------------------------------
|
||||
{
|
||||
"encoding" : "UTF-8",
|
||||
"plug-ins" : [
|
||||
"python",
|
||||
"c++",
|
||||
"ruby"
|
||||
],
|
||||
"indent" : {
|
||||
"length" : 3,
|
||||
"use_space" : true
|
||||
}
|
||||
}
|
||||
------------------------------------------------------------------------
|
||||
|
||||
We would first call the parse-method:
|
||||
|
||||
------------------------------------------------------------------------
|
||||
//Say that we're using the QtJson namespace
|
||||
using namespace QtJson;
|
||||
bool ok;
|
||||
//json is a QString containing the JSON data
|
||||
QVariantMap result = Json::parse(json, ok).toMap();
|
||||
|
||||
if(!ok) {
|
||||
qFatal("An error occurred during parsing");
|
||||
exit(1);
|
||||
}
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Assuming the parsing process completed without errors, we would then
|
||||
go through the hierarchy:
|
||||
|
||||
------------------------------------------------------------------------
|
||||
qDebug() << "encoding:" << result["encoding"].toString();
|
||||
qDebug() << "plugins:";
|
||||
|
||||
foreach(QVariant plugin, result["plug-ins"].toList()) {
|
||||
qDebug() << "\t-" << plugin.toString();
|
||||
}
|
||||
|
||||
QVariantMap nestedMap = result["indent"].toMap();
|
||||
qDebug() << "length:" << nestedMap["length"].toInt();
|
||||
qDebug() << "use_space:" << nestedMap["use_space"].toBool();
|
||||
------------------------------------------------------------------------
|
||||
|
||||
The previous code would print out the following:
|
||||
|
||||
------------------------------------------------------------------------
|
||||
encoding: "UTF-8"
|
||||
plugins:
|
||||
- "python"
|
||||
- "c++"
|
||||
- "ruby"
|
||||
length: 3
|
||||
use_space: true
|
||||
------------------------------------------------------------------------
|
||||
|
||||
To write JSON data from Qt object is as simple as parsing:
|
||||
|
||||
------------------------------------------------------------------------
|
||||
QVariantMap map;
|
||||
map["name"] = "Name";
|
||||
map["age"] = 22;
|
||||
|
||||
QByteArray data = Json::serialize(map);
|
||||
------------------------------------------------------------------------
|
||||
|
||||
The byte array 'data' contains valid JSON data:
|
||||
|
||||
------------------------------------------------------------------------
|
||||
{
|
||||
name: "Luis Gustavo",
|
||||
age: 22,
|
||||
}
|
||||
------------------------------------------------------------------------
|
||||
|
||||
|
||||
########################################################################
|
||||
4. CONTRIBUTING
|
||||
|
||||
The code is available to download at GitHub. Contribute if you dare!
|
||||
@@ -0,0 +1,573 @@
|
||||
/* Copyright 2011 Eeli Reilin. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
* EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* The views and conclusions contained in the software and documentation
|
||||
* are those of the authors and should not be interpreted as representing
|
||||
* official policies, either expressed or implied, of Eeli Reilin.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file json.cpp
|
||||
*/
|
||||
|
||||
#include "json.h"
|
||||
|
||||
#include <QMetaType>
|
||||
#include <iostream>
|
||||
|
||||
namespace QtJson
|
||||
{
|
||||
|
||||
static QString sanitizeString(QString str)
|
||||
{
|
||||
str.replace(QLatin1String("\\"), QLatin1String("\\\\"));
|
||||
str.replace(QLatin1String("\""), QLatin1String("\\\""));
|
||||
str.replace(QLatin1String("\b"), QLatin1String("\\b"));
|
||||
str.replace(QLatin1String("\f"), QLatin1String("\\f"));
|
||||
str.replace(QLatin1String("\n"), QLatin1String("\\n"));
|
||||
str.replace(QLatin1String("\r"), QLatin1String("\\r"));
|
||||
str.replace(QLatin1String("\t"), QLatin1String("\\t"));
|
||||
return QString(QLatin1String("\"%1\"")).arg(str);
|
||||
}
|
||||
|
||||
static QByteArray join(const QList<QByteArray> &list, const QByteArray &sep)
|
||||
{
|
||||
QByteArray res;
|
||||
for (const QByteArray &i : list) {
|
||||
if (!res.isEmpty()) {
|
||||
res += sep;
|
||||
}
|
||||
res += i;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse
|
||||
*/
|
||||
QVariant Json::parse(const QString &json)
|
||||
{
|
||||
bool success = true;
|
||||
return Json::parse(json, success);
|
||||
}
|
||||
|
||||
/**
|
||||
* parse
|
||||
*/
|
||||
QVariant Json::parse(const QString &json, bool &success)
|
||||
{
|
||||
success = true;
|
||||
|
||||
// Return an empty QVariant if the JSON data is either null or empty
|
||||
if (!json.isNull() || !json.isEmpty()) {
|
||||
// We'll start from index 0
|
||||
int index = 0;
|
||||
|
||||
// Parse the first value
|
||||
QVariant value = Json::parseValue(json, index, success);
|
||||
|
||||
// Return the parsed value
|
||||
return value;
|
||||
} else {
|
||||
// Return the empty QVariant
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray Json::serialize(const QVariant &data)
|
||||
{
|
||||
bool success = true;
|
||||
return Json::serialize(data, success);
|
||||
}
|
||||
|
||||
QByteArray Json::serialize(const QVariant &data, bool &success)
|
||||
{
|
||||
QByteArray str;
|
||||
success = true;
|
||||
|
||||
if (!data.isValid()) // invalid or null?
|
||||
{
|
||||
str = "null";
|
||||
}
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
else if ((data.typeId() == QMetaType::Type::QVariantList) ||
|
||||
(data.typeId() == QMetaType::Type::QStringList)) // variant is a list?
|
||||
#else
|
||||
else if ((data.type() == QVariant::List) || (data.type() == QVariant::StringList)) // variant is a list?
|
||||
#endif
|
||||
{
|
||||
QList<QByteArray> values;
|
||||
const QVariantList list = data.toList();
|
||||
for (const QVariant &v : list) {
|
||||
QByteArray serializedValue = serialize(v);
|
||||
if (serializedValue.isNull()) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
values << serializedValue;
|
||||
}
|
||||
|
||||
str = "[ " + join(values, ", ") + " ]";
|
||||
}
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
else if ((data.typeId() == QMetaType::Type::QVariantHash)) // variant is a list?
|
||||
#else
|
||||
else if (data.type() == QVariant::Hash) // variant is a hash?
|
||||
#endif
|
||||
{
|
||||
const QVariantHash vhash = data.toHash();
|
||||
QHashIterator<QString, QVariant> it(vhash);
|
||||
str = "{ ";
|
||||
QList<QByteArray> pairs;
|
||||
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
QByteArray serializedValue = serialize(it.value());
|
||||
|
||||
if (serializedValue.isNull()) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue;
|
||||
}
|
||||
|
||||
str += join(pairs, ", ");
|
||||
str += " }";
|
||||
}
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
else if ((data.typeId() == QMetaType::Type::QVariantMap)) // variant is a list?
|
||||
#else
|
||||
else if (data.type() == QVariant::Map) // variant is a map?
|
||||
#endif
|
||||
{
|
||||
const QVariantMap vmap = data.toMap();
|
||||
QMapIterator<QString, QVariant> it(vmap);
|
||||
str = "{ ";
|
||||
QList<QByteArray> pairs;
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
QByteArray serializedValue = serialize(it.value());
|
||||
if (serializedValue.isNull()) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue;
|
||||
}
|
||||
str += join(pairs, ", ");
|
||||
str += " }";
|
||||
}
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
else if ((data.typeId() == QMetaType::Type::QString) ||
|
||||
(data.typeId() == QMetaType::Type::QByteArray)) // variant is a list?
|
||||
#else
|
||||
else if ((data.type() == QVariant::String) || (data.type() == QVariant::ByteArray)) // a string or a byte array?
|
||||
#endif
|
||||
{
|
||||
str = sanitizeString(data.toString()).toUtf8();
|
||||
}
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
else if (data.typeId() == QMetaType::Type::Double)
|
||||
#else
|
||||
else if (data.type() == QVariant::Double) // double?
|
||||
#endif
|
||||
{
|
||||
str = QByteArray::number(data.toDouble(), 'g', 20);
|
||||
if (!str.contains(".") && !str.contains("e")) {
|
||||
str += ".0";
|
||||
}
|
||||
}
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
else if (data.typeId() == QMetaType::Type::Bool)
|
||||
#else
|
||||
else if (data.type() == QVariant::Bool) // boolean value?
|
||||
#endif
|
||||
{
|
||||
str = data.toBool() ? "true" : "false";
|
||||
}
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
else if (data.typeId() == QMetaType::Type::ULongLong)
|
||||
#else
|
||||
else if (data.type() == QVariant::ULongLong) // large unsigned number?
|
||||
#endif
|
||||
{
|
||||
str = QByteArray::number(data.value<qulonglong>());
|
||||
} else if (data.canConvert<qlonglong>()) // any signed number?
|
||||
{
|
||||
str = QByteArray::number(data.value<qlonglong>());
|
||||
} else if (data.canConvert<long>()) {
|
||||
str = QString::number(data.value<long>()).toUtf8();
|
||||
} else if (data.canConvert<QString>()) // can value be converted to string?
|
||||
{
|
||||
// this will catch QDate, QDateTime, QUrl, ...
|
||||
str = sanitizeString(data.toString()).toUtf8();
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
if (success) {
|
||||
return str;
|
||||
} else {
|
||||
return QByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parseValue
|
||||
*/
|
||||
QVariant Json::parseValue(const QString &json, int &index, bool &success)
|
||||
{
|
||||
// Determine what kind of data we should parse by
|
||||
// checking out the upcoming token
|
||||
switch (Json::lookAhead(json, index)) {
|
||||
case JsonTokenString:
|
||||
return Json::parseString(json, index, success);
|
||||
case JsonTokenNumber:
|
||||
return Json::parseNumber(json, index);
|
||||
case JsonTokenCurlyOpen:
|
||||
return Json::parseObject(json, index, success);
|
||||
case JsonTokenSquaredOpen:
|
||||
return Json::parseArray(json, index, success);
|
||||
case JsonTokenTrue:
|
||||
Json::nextToken(json, index);
|
||||
return QVariant(true);
|
||||
case JsonTokenFalse:
|
||||
Json::nextToken(json, index);
|
||||
return QVariant(false);
|
||||
case JsonTokenNull:
|
||||
Json::nextToken(json, index);
|
||||
return QVariant();
|
||||
case JsonTokenNone:
|
||||
break;
|
||||
}
|
||||
|
||||
// If there were no tokens, flag the failure and return an empty QVariant
|
||||
success = false;
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
/**
|
||||
* parseObject
|
||||
*/
|
||||
QVariant Json::parseObject(const QString &json, int &index, bool &success)
|
||||
{
|
||||
QVariantMap map;
|
||||
int token;
|
||||
|
||||
// Get rid of the whitespace and increment index
|
||||
Json::nextToken(json, index);
|
||||
|
||||
// Loop through all of the key/value pairs of the object
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
// Get the upcoming token
|
||||
token = Json::lookAhead(json, index);
|
||||
|
||||
if (token == JsonTokenNone) {
|
||||
success = false;
|
||||
return QVariantMap();
|
||||
} else if (token == JsonTokenComma) {
|
||||
Json::nextToken(json, index);
|
||||
} else if (token == JsonTokenCurlyClose) {
|
||||
Json::nextToken(json, index);
|
||||
return map;
|
||||
} else {
|
||||
// Parse the key/value pair's name
|
||||
QString name = Json::parseString(json, index, success).toString();
|
||||
|
||||
if (!success) {
|
||||
return QVariantMap();
|
||||
}
|
||||
|
||||
// Get the next token
|
||||
token = Json::nextToken(json, index);
|
||||
|
||||
// If the next token is not a colon, flag the failure
|
||||
// return an empty QVariant
|
||||
if (token != JsonTokenColon) {
|
||||
success = false;
|
||||
return QVariant(QVariantMap());
|
||||
}
|
||||
|
||||
// Parse the key/value pair's value
|
||||
QVariant value = Json::parseValue(json, index, success);
|
||||
|
||||
if (!success) {
|
||||
return QVariantMap();
|
||||
}
|
||||
|
||||
// Assign the value to the key in the map
|
||||
map[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the map successfully
|
||||
return QVariant(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* parseArray
|
||||
*/
|
||||
QVariant Json::parseArray(const QString &json, int &index, bool &success)
|
||||
{
|
||||
QVariantList list;
|
||||
|
||||
Json::nextToken(json, index);
|
||||
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
int token = Json::lookAhead(json, index);
|
||||
|
||||
if (token == JsonTokenNone) {
|
||||
success = false;
|
||||
return QVariantList();
|
||||
} else if (token == JsonTokenComma) {
|
||||
Json::nextToken(json, index);
|
||||
} else if (token == JsonTokenSquaredClose) {
|
||||
Json::nextToken(json, index);
|
||||
break;
|
||||
} else {
|
||||
QVariant value = Json::parseValue(json, index, success);
|
||||
|
||||
if (!success) {
|
||||
return QVariantList();
|
||||
}
|
||||
|
||||
list.push_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* parseString
|
||||
*/
|
||||
QVariant Json::parseString(const QString &json, int &index, bool &success)
|
||||
{
|
||||
QString s;
|
||||
QChar c;
|
||||
|
||||
Json::eatWhitespace(json, index);
|
||||
|
||||
c = json[index++];
|
||||
|
||||
bool complete = false;
|
||||
while (!complete) {
|
||||
if (index == json.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
c = json[index++];
|
||||
|
||||
if (c == '\"') {
|
||||
complete = true;
|
||||
break;
|
||||
} else if (c == '\\') {
|
||||
if (index == json.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
c = json[index++];
|
||||
|
||||
if (c == '\"') {
|
||||
s.append('\"');
|
||||
} else if (c == '\\') {
|
||||
s.append('\\');
|
||||
} else if (c == '/') {
|
||||
s.append('/');
|
||||
} else if (c == 'b') {
|
||||
s.append('\b');
|
||||
} else if (c == 'f') {
|
||||
s.append('\f');
|
||||
} else if (c == 'n') {
|
||||
s.append('\n');
|
||||
} else if (c == 'r') {
|
||||
s.append('\r');
|
||||
} else if (c == 't') {
|
||||
s.append('\t');
|
||||
} else if (c == 'u') {
|
||||
int remainingLength = json.size() - index;
|
||||
|
||||
if (remainingLength >= 4) {
|
||||
QString unicodeStr = json.mid(index, 4);
|
||||
|
||||
int symbol = unicodeStr.toInt(0, 16);
|
||||
|
||||
s.append(QChar(symbol));
|
||||
|
||||
index += 4;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (!complete) {
|
||||
success = false;
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
return QVariant(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* parseNumber
|
||||
*/
|
||||
QVariant Json::parseNumber(const QString &json, int &index)
|
||||
{
|
||||
Json::eatWhitespace(json, index);
|
||||
|
||||
int lastIndex = Json::lastIndexOfNumber(json, index);
|
||||
int charLength = (lastIndex - index) + 1;
|
||||
QString numberStr;
|
||||
|
||||
numberStr = json.mid(index, charLength);
|
||||
|
||||
index = lastIndex + 1;
|
||||
|
||||
if (numberStr.contains('.')) {
|
||||
return QVariant(numberStr.toDouble(NULL));
|
||||
} else if (numberStr.startsWith('-')) {
|
||||
return QVariant(numberStr.toLongLong(NULL));
|
||||
} else {
|
||||
return QVariant(numberStr.toULongLong(NULL));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* lastIndexOfNumber
|
||||
*/
|
||||
int Json::lastIndexOfNumber(const QString &json, int index)
|
||||
{
|
||||
static const QString numericCharacters("0123456789+-.eE");
|
||||
int lastIndex;
|
||||
|
||||
for (lastIndex = index; lastIndex < json.size(); lastIndex++) {
|
||||
if (numericCharacters.indexOf(json[lastIndex]) == -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return lastIndex - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* eatWhitespace
|
||||
*/
|
||||
void Json::eatWhitespace(const QString &json, int &index)
|
||||
{
|
||||
static const QString whitespaceChars(" \t\n\r");
|
||||
for (; index < json.size(); index++) {
|
||||
if (whitespaceChars.indexOf(json[index]) == -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* lookAhead
|
||||
*/
|
||||
int Json::lookAhead(const QString &json, int index)
|
||||
{
|
||||
int saveIndex = index;
|
||||
return Json::nextToken(json, saveIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* nextToken
|
||||
*/
|
||||
int Json::nextToken(const QString &json, int &index)
|
||||
{
|
||||
Json::eatWhitespace(json, index);
|
||||
|
||||
if (index == json.size()) {
|
||||
return JsonTokenNone;
|
||||
}
|
||||
|
||||
QChar c = json[index];
|
||||
index++;
|
||||
switch (c.toLatin1()) {
|
||||
case '{':
|
||||
return JsonTokenCurlyOpen;
|
||||
case '}':
|
||||
return JsonTokenCurlyClose;
|
||||
case '[':
|
||||
return JsonTokenSquaredOpen;
|
||||
case ']':
|
||||
return JsonTokenSquaredClose;
|
||||
case ',':
|
||||
return JsonTokenComma;
|
||||
case '"':
|
||||
return JsonTokenString;
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '-':
|
||||
return JsonTokenNumber;
|
||||
case ':':
|
||||
return JsonTokenColon;
|
||||
}
|
||||
|
||||
index--;
|
||||
|
||||
int remainingLength = json.size() - index;
|
||||
|
||||
// True
|
||||
if (remainingLength >= 4) {
|
||||
if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') {
|
||||
index += 4;
|
||||
return JsonTokenTrue;
|
||||
}
|
||||
}
|
||||
|
||||
// False
|
||||
if (remainingLength >= 5) {
|
||||
if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' &&
|
||||
json[index + 4] == 'e') {
|
||||
index += 5;
|
||||
return JsonTokenFalse;
|
||||
}
|
||||
}
|
||||
|
||||
// Null
|
||||
if (remainingLength >= 4) {
|
||||
if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') {
|
||||
index += 4;
|
||||
return JsonTokenNull;
|
||||
}
|
||||
}
|
||||
|
||||
return JsonTokenNone;
|
||||
}
|
||||
|
||||
} // namespace QtJson
|
||||
@@ -0,0 +1,204 @@
|
||||
/* Copyright 2011 Eeli Reilin. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
* EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* The views and conclusions contained in the software and documentation
|
||||
* are those of the authors and should not be interpreted as representing
|
||||
* official policies, either expressed or implied, of Eeli Reilin.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file json.h
|
||||
*/
|
||||
|
||||
#ifndef JSON_H
|
||||
#define JSON_H
|
||||
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
|
||||
namespace QtJson
|
||||
{
|
||||
|
||||
/**
|
||||
* \enum JsonToken
|
||||
*/
|
||||
enum JsonToken
|
||||
{
|
||||
JsonTokenNone = 0,
|
||||
JsonTokenCurlyOpen = 1,
|
||||
JsonTokenCurlyClose = 2,
|
||||
JsonTokenSquaredOpen = 3,
|
||||
JsonTokenSquaredClose = 4,
|
||||
JsonTokenColon = 5,
|
||||
JsonTokenComma = 6,
|
||||
JsonTokenString = 7,
|
||||
JsonTokenNumber = 8,
|
||||
JsonTokenTrue = 9,
|
||||
JsonTokenFalse = 10,
|
||||
JsonTokenNull = 11
|
||||
};
|
||||
|
||||
/**
|
||||
* \class Json
|
||||
* \brief A JSON data parser
|
||||
*
|
||||
* Json parses a JSON data into a QVariant hierarchy.
|
||||
*/
|
||||
class Json
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Parse a JSON string
|
||||
*
|
||||
* \param json The JSON data
|
||||
*/
|
||||
static QVariant parse(const QString &json);
|
||||
|
||||
/**
|
||||
* Parse a JSON string
|
||||
*
|
||||
* \param json The JSON data
|
||||
* \param success The success of the parsing
|
||||
*/
|
||||
static QVariant parse(const QString &json, bool &success);
|
||||
|
||||
/**
|
||||
* This method generates a textual JSON representation
|
||||
*
|
||||
* \param data The JSON data generated by the parser.
|
||||
* \param success The success of the serialization
|
||||
*/
|
||||
static QByteArray serialize(const QVariant &data);
|
||||
|
||||
/**
|
||||
* This method generates a textual JSON representation
|
||||
*
|
||||
* \param data The JSON data generated by the parser.
|
||||
* \param success The success of the serialization
|
||||
*
|
||||
* \return QByteArray Textual JSON representation
|
||||
*/
|
||||
static QByteArray serialize(const QVariant &data, bool &success);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Parses a value starting from index
|
||||
*
|
||||
* \param json The JSON data
|
||||
* \param index The start index
|
||||
* \param success The success of the parse process
|
||||
*
|
||||
* \return QVariant The parsed value
|
||||
*/
|
||||
static QVariant parseValue(const QString &json, int &index,
|
||||
bool &success);
|
||||
|
||||
/**
|
||||
* Parses an object starting from index
|
||||
*
|
||||
* \param json The JSON data
|
||||
* \param index The start index
|
||||
* \param success The success of the object parse
|
||||
*
|
||||
* \return QVariant The parsed object map
|
||||
*/
|
||||
static QVariant parseObject(const QString &json, int &index,
|
||||
bool &success);
|
||||
|
||||
/**
|
||||
* Parses an array starting from index
|
||||
*
|
||||
* \param json The JSON data
|
||||
* \param index The starting index
|
||||
* \param success The success of the array parse
|
||||
*
|
||||
* \return QVariant The parsed variant array
|
||||
*/
|
||||
static QVariant parseArray(const QString &json, int &index,
|
||||
bool &success);
|
||||
|
||||
/**
|
||||
* Parses a string starting from index
|
||||
*
|
||||
* \param json The JSON data
|
||||
* \param index The starting index
|
||||
* \param success The success of the string parse
|
||||
*
|
||||
* \return QVariant The parsed string
|
||||
*/
|
||||
static QVariant parseString(const QString &json, int &index,
|
||||
bool &success);
|
||||
|
||||
/**
|
||||
* Parses a number starting from index
|
||||
*
|
||||
* \param json The JSON data
|
||||
* \param index The starting index
|
||||
*
|
||||
* \return QVariant The parsed number
|
||||
*/
|
||||
static QVariant parseNumber(const QString &json, int &index);
|
||||
|
||||
/**
|
||||
* Get the last index of a number starting from index
|
||||
*
|
||||
* \param json The JSON data
|
||||
* \param index The starting index
|
||||
*
|
||||
* \return The last index of the number
|
||||
*/
|
||||
static int lastIndexOfNumber(const QString &json, int index);
|
||||
|
||||
/**
|
||||
* Skip unwanted whitespace symbols starting from index
|
||||
*
|
||||
* \param json The JSON data
|
||||
* \param index The start index
|
||||
*/
|
||||
static void eatWhitespace(const QString &json, int &index);
|
||||
|
||||
/**
|
||||
* Check what token lies ahead
|
||||
*
|
||||
* \param json The JSON data
|
||||
* \param index The starting index
|
||||
*
|
||||
* \return int The upcoming token
|
||||
*/
|
||||
static int lookAhead(const QString &json, int index);
|
||||
|
||||
/**
|
||||
* Get the next JSON token
|
||||
*
|
||||
* \param json The JSON data
|
||||
* \param index The starting index
|
||||
*
|
||||
* \return int The next JSON token
|
||||
*/
|
||||
static int nextToken(const QString &json, int &index);
|
||||
};
|
||||
|
||||
|
||||
} //end namespace
|
||||
|
||||
#endif //JSON_H
|
||||
Executable
+1425
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,155 @@
|
||||
/****************************************************************************
|
||||
** Filename: unzip.h
|
||||
** Last updated [dd/mm/yyyy]: 27/03/2011
|
||||
**
|
||||
** pkzip 2.0 decompression.
|
||||
**
|
||||
** Some of the code has been inspired by other open source projects,
|
||||
** (mainly Info-Zip and Gilles Vollant's minizip).
|
||||
** Compression and decompression actually uses the zlib library.
|
||||
**
|
||||
** Copyright (C) 2007-2012 Angius Fabrizio. All rights reserved.
|
||||
**
|
||||
** This file is part of the OSDaB project (http://osdab.42cows.org/).
|
||||
**
|
||||
** This file may be distributed and/or modified under the terms of the
|
||||
** GNU General Public License version 2 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file.
|
||||
**
|
||||
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
||||
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
**
|
||||
** See the file LICENSE.GPL that came with this software distribution or
|
||||
** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information.
|
||||
**
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef OSDAB_UNZIP__H
|
||||
#define OSDAB_UNZIP__H
|
||||
|
||||
#include "zipglobal.h"
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QtGlobal>
|
||||
#include <zlib.h>
|
||||
|
||||
class QDir;
|
||||
class QFile;
|
||||
class QIODevice;
|
||||
class QString;
|
||||
|
||||
OSDAB_BEGIN_NAMESPACE(Zip)
|
||||
|
||||
class UnzipPrivate;
|
||||
|
||||
class OSDAB_ZIP_EXPORT UnZip
|
||||
{
|
||||
public:
|
||||
enum ErrorCode
|
||||
{
|
||||
Ok,
|
||||
ZlibInit,
|
||||
ZlibError,
|
||||
OpenFailed,
|
||||
PartiallyCorrupted,
|
||||
Corrupted,
|
||||
WrongPassword,
|
||||
NoOpenArchive,
|
||||
FileNotFound,
|
||||
ReadFailed,
|
||||
WriteFailed,
|
||||
SeekFailed,
|
||||
CreateDirFailed,
|
||||
InvalidDevice,
|
||||
InvalidArchive,
|
||||
HeaderConsistencyError,
|
||||
|
||||
Skip,
|
||||
SkipAll // internal use only
|
||||
};
|
||||
|
||||
enum ExtractionOption
|
||||
{
|
||||
ExtractPaths = 0x0001,
|
||||
SkipPaths = 0x0002,
|
||||
VerifyOnly = 0x0004,
|
||||
NoSilentDirectoryCreation = 0x0008
|
||||
};
|
||||
Q_DECLARE_FLAGS(ExtractionOptions, ExtractionOption)
|
||||
|
||||
enum CompressionMethod
|
||||
{
|
||||
NoCompression,
|
||||
Deflated,
|
||||
UnknownCompression
|
||||
};
|
||||
|
||||
enum FileType
|
||||
{
|
||||
File,
|
||||
Directory
|
||||
};
|
||||
|
||||
struct ZipEntry
|
||||
{
|
||||
ZipEntry();
|
||||
|
||||
QString filename;
|
||||
QString comment;
|
||||
|
||||
quint32 compressedSize;
|
||||
quint32 uncompressedSize;
|
||||
quint32 crc32;
|
||||
|
||||
QDateTime lastModified;
|
||||
|
||||
CompressionMethod compression;
|
||||
FileType type;
|
||||
|
||||
bool encrypted;
|
||||
};
|
||||
|
||||
UnZip();
|
||||
virtual ~UnZip();
|
||||
|
||||
bool isOpen() const;
|
||||
|
||||
ErrorCode openArchive(const QString &filename);
|
||||
ErrorCode openArchive(QIODevice *device);
|
||||
void closeArchive();
|
||||
|
||||
QString archiveComment() const;
|
||||
|
||||
QString formatError(UnZip::ErrorCode c) const;
|
||||
|
||||
bool contains(const QString &file) const;
|
||||
|
||||
QStringList fileList() const;
|
||||
QList<ZipEntry> entryList() const;
|
||||
|
||||
ErrorCode verifyArchive();
|
||||
|
||||
ErrorCode extractAll(const QString &dirname, ExtractionOptions options = ExtractPaths);
|
||||
ErrorCode extractAll(const QDir &dir, ExtractionOptions options = ExtractPaths);
|
||||
|
||||
ErrorCode extractFile(const QString &filename, const QString &dirname, ExtractionOptions options = ExtractPaths);
|
||||
ErrorCode extractFile(const QString &filename, const QDir &dir, ExtractionOptions options = ExtractPaths);
|
||||
ErrorCode extractFile(const QString &filename, QIODevice *device, ExtractionOptions options = ExtractPaths);
|
||||
|
||||
ErrorCode
|
||||
extractFiles(const QStringList &filenames, const QString &dirname, ExtractionOptions options = ExtractPaths);
|
||||
ErrorCode extractFiles(const QStringList &filenames, const QDir &dir, ExtractionOptions options = ExtractPaths);
|
||||
|
||||
void setPassword(const QString &pwd);
|
||||
|
||||
private:
|
||||
UnzipPrivate *d;
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(UnZip::ExtractionOptions)
|
||||
|
||||
OSDAB_END_NAMESPACE
|
||||
|
||||
#endif // OSDAB_UNZIP__H
|
||||
Executable
+130
@@ -0,0 +1,130 @@
|
||||
/****************************************************************************
|
||||
** Filename: unzip_p.h
|
||||
** Last updated [dd/mm/yyyy]: 27/03/2011
|
||||
**
|
||||
** pkzip 2.0 decompression.
|
||||
**
|
||||
** Some of the code has been inspired by other open source projects,
|
||||
** (mainly Info-Zip and Gilles Vollant's minizip).
|
||||
** Compression and decompression actually uses the zlib library.
|
||||
**
|
||||
** Copyright (C) 2007-2012 Angius Fabrizio. All rights reserved.
|
||||
**
|
||||
** This file is part of the OSDaB project (http://osdab.42cows.org/).
|
||||
**
|
||||
** This file may be distributed and/or modified under the terms of the
|
||||
** GNU General Public License version 2 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file.
|
||||
**
|
||||
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
||||
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
**
|
||||
** See the file LICENSE.GPL that came with this software distribution or
|
||||
** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information.
|
||||
**
|
||||
**********************************************************************/
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Zip/UnZip API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#ifndef OSDAB_UNZIP_P__H
|
||||
#define OSDAB_UNZIP_P__H
|
||||
|
||||
#include "unzip.h"
|
||||
#include "zipentry_p.h"
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QtGlobal>
|
||||
|
||||
// zLib authors suggest using larger buffers (128K or 256K) for (de)compression (especially for inflate())
|
||||
// we use a 256K buffer here - if you want to use this code on a pre-iceage mainframe please change it ;)
|
||||
#define UNZIP_READ_BUFFER (256*1024)
|
||||
|
||||
OSDAB_BEGIN_NAMESPACE(Zip)
|
||||
|
||||
class UnzipPrivate : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
UnzipPrivate();
|
||||
|
||||
// Replace this with whatever else you use to store/retrieve the password.
|
||||
QString password;
|
||||
|
||||
bool skipAllEncrypted;
|
||||
|
||||
QMap<QString,ZipEntryP*>* headers;
|
||||
|
||||
QIODevice* device;
|
||||
QFile* file;
|
||||
|
||||
char buffer1[UNZIP_READ_BUFFER];
|
||||
char buffer2[UNZIP_READ_BUFFER];
|
||||
|
||||
unsigned char* uBuffer;
|
||||
const quint32* crcTable;
|
||||
|
||||
// Central Directory (CD) offset
|
||||
quint32 cdOffset;
|
||||
// End of Central Directory (EOCD) offset
|
||||
quint32 eocdOffset;
|
||||
|
||||
// Number of entries in the Central Directory (as to the EOCD record)
|
||||
quint16 cdEntryCount;
|
||||
|
||||
// The number of detected entries that have been skipped because of a non compatible format
|
||||
quint16 unsupportedEntryCount;
|
||||
|
||||
QString comment;
|
||||
|
||||
UnZip::ErrorCode openArchive(QIODevice* device);
|
||||
|
||||
UnZip::ErrorCode seekToCentralDirectory();
|
||||
UnZip::ErrorCode parseCentralDirectoryRecord();
|
||||
UnZip::ErrorCode parseLocalHeaderRecord(const QString& path, const ZipEntryP& entry);
|
||||
|
||||
void closeArchive();
|
||||
|
||||
UnZip::ErrorCode extractFile(const QString& path, const ZipEntryP& entry, const QDir& dir, UnZip::ExtractionOptions options);
|
||||
UnZip::ErrorCode extractFile(const QString& path, const ZipEntryP& entry, QIODevice* device, UnZip::ExtractionOptions options);
|
||||
|
||||
UnZip::ErrorCode testPassword(quint32* keys, const QString&_file, const ZipEntryP& header);
|
||||
bool testKeys(const ZipEntryP& header, quint32* keys);
|
||||
|
||||
bool createDirectory(const QString& path);
|
||||
|
||||
inline void decryptBytes(quint32* keys, char* buffer, qint64 read);
|
||||
|
||||
inline quint32 getULong(const unsigned char* data, quint32 offset) const;
|
||||
inline quint64 getULLong(const unsigned char* data, quint32 offset) const;
|
||||
inline quint16 getUShort(const unsigned char* data, quint32 offset) const;
|
||||
inline int decryptByte(quint32 key2) const;
|
||||
inline void updateKeys(quint32* keys, int c) const;
|
||||
inline void initKeys(const QString& pwd, quint32* keys) const;
|
||||
|
||||
inline QDateTime convertDateTime(const unsigned char date[2], const unsigned char time[2]) const;
|
||||
|
||||
private slots:
|
||||
void deviceDestroyed(QObject*);
|
||||
|
||||
private:
|
||||
UnZip::ErrorCode extractStoredFile(const quint32 szComp, quint32** keys,
|
||||
quint32& myCRC, QIODevice* outDev, UnZip::ExtractionOptions options);
|
||||
UnZip::ErrorCode inflateFile(const quint32 szComp, quint32** keys,
|
||||
quint32& myCRC, QIODevice* outDev, UnZip::ExtractionOptions options);
|
||||
void do_closeArchive();
|
||||
};
|
||||
|
||||
OSDAB_END_NAMESPACE
|
||||
|
||||
#endif // OSDAB_UNZIP_P__H
|
||||
Executable
+1619
File diff suppressed because it is too large
Load Diff
Executable
+158
@@ -0,0 +1,158 @@
|
||||
/****************************************************************************
|
||||
** Filename: zip.h
|
||||
** Last updated [dd/mm/yyyy]: 27/03/2011
|
||||
**
|
||||
** pkzip 2.0 file compression.
|
||||
**
|
||||
** Some of the code has been inspired by other open source projects,
|
||||
** (mainly Info-Zip and Gilles Vollant's minizip).
|
||||
** Compression and decompression actually uses the zlib library.
|
||||
**
|
||||
** Copyright (C) 2007-2012 Angius Fabrizio. All rights reserved.
|
||||
**
|
||||
** This file is part of the OSDaB project (http://osdab.42cows.org/).
|
||||
**
|
||||
** This file may be distributed and/or modified under the terms of the
|
||||
** GNU General Public License version 2 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file.
|
||||
**
|
||||
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
||||
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
**
|
||||
** See the file LICENSE.GPL that came with this software distribution or
|
||||
** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information.
|
||||
**
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef OSDAB_ZIP__H
|
||||
#define OSDAB_ZIP__H
|
||||
|
||||
#include "zipglobal.h"
|
||||
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QtGlobal>
|
||||
|
||||
#include <zlib/zlib.h>
|
||||
|
||||
class QIODevice;
|
||||
class QFile;
|
||||
class QDir;
|
||||
class QStringList;
|
||||
class QString;
|
||||
|
||||
OSDAB_BEGIN_NAMESPACE(Zip)
|
||||
|
||||
class ZipPrivate;
|
||||
|
||||
class OSDAB_ZIP_EXPORT Zip
|
||||
{
|
||||
public:
|
||||
enum ErrorCode
|
||||
{
|
||||
Ok,
|
||||
ZlibInit,
|
||||
ZlibError,
|
||||
FileExists,
|
||||
OpenFailed,
|
||||
NoOpenArchive,
|
||||
FileNotFound,
|
||||
ReadFailed,
|
||||
WriteFailed,
|
||||
SeekFailed,
|
||||
InternalError
|
||||
};
|
||||
|
||||
enum CompressionLevel
|
||||
{
|
||||
Store,
|
||||
Deflate1 = 1, Deflate2, Deflate3, Deflate4,
|
||||
Deflate5, Deflate6, Deflate7, Deflate8, Deflate9,
|
||||
AutoCPU, AutoMIME, AutoFull
|
||||
};
|
||||
|
||||
enum CompressionOption
|
||||
{
|
||||
/*! Does not preserve absolute paths in the zip file when adding a
|
||||
file or directory (default) */
|
||||
RelativePaths = 0x0001,
|
||||
/*! Preserve absolute paths */
|
||||
AbsolutePaths = 0x0002,
|
||||
/*! Do not store paths. All the files are put in the (evtl. user defined)
|
||||
root of the zip file */
|
||||
IgnorePaths = 0x0004,
|
||||
/*! Works only with addDirectory(). Adds the directory's contents,
|
||||
including subdirectories, but does not add an entry for the root
|
||||
directory itself. */
|
||||
IgnoreRoot = 0x0008,
|
||||
/*! Used only when compressing a directory or multiple files.
|
||||
If set invalid or unreadable files are simply skipped.
|
||||
*/
|
||||
SkipBadFiles = 0x0020,
|
||||
/*! Makes sure a file is never added twice to the same zip archive.
|
||||
This check is only necessary in certain usage scenarios and given
|
||||
that it slows down processing you need to enable it explicitly with
|
||||
this flag.
|
||||
*/
|
||||
CheckForDuplicates = 0x0040
|
||||
};
|
||||
Q_DECLARE_FLAGS(CompressionOptions, CompressionOption)
|
||||
|
||||
Zip();
|
||||
virtual ~Zip();
|
||||
|
||||
bool isOpen() const;
|
||||
|
||||
void setPassword(const QString& pwd);
|
||||
void clearPassword();
|
||||
QString password() const;
|
||||
|
||||
ErrorCode createArchive(const QString& file, bool overwrite = true);
|
||||
ErrorCode createArchive(QIODevice* device);
|
||||
|
||||
QString archiveComment() const;
|
||||
void setArchiveComment(const QString& comment);
|
||||
|
||||
ErrorCode addDirectoryContents(const QString& path,
|
||||
CompressionLevel level = AutoFull);
|
||||
ErrorCode addDirectoryContents(const QString& path, const QString& root,
|
||||
CompressionLevel level = AutoFull);
|
||||
|
||||
ErrorCode addDirectory(const QString& path,
|
||||
CompressionLevel level = AutoFull);
|
||||
ErrorCode addDirectory(const QString& path, const QString& root,
|
||||
CompressionLevel level = AutoFull);
|
||||
ErrorCode addDirectory(const QString& path, const QString& root,
|
||||
CompressionOptions options, CompressionLevel level = AutoFull,
|
||||
int* addedFiles = 0);
|
||||
|
||||
ErrorCode addFile(const QString& path,
|
||||
CompressionLevel level = AutoFull);
|
||||
ErrorCode addFile(const QString& path, const QString& root,
|
||||
CompressionLevel level = AutoFull);
|
||||
ErrorCode addFile(const QString& path, const QString& root,
|
||||
CompressionOptions options,
|
||||
CompressionLevel level = AutoFull);
|
||||
|
||||
ErrorCode addFiles(const QStringList& paths,
|
||||
CompressionLevel level = AutoFull);
|
||||
ErrorCode addFiles(const QStringList& paths, const QString& root,
|
||||
CompressionLevel level = AutoFull);
|
||||
ErrorCode addFiles(const QStringList& paths, const QString& root,
|
||||
CompressionOptions options,
|
||||
CompressionLevel level = AutoFull,
|
||||
int* addedFiles = 0);
|
||||
|
||||
ErrorCode closeArchive();
|
||||
|
||||
QString formatError(ErrorCode c) const;
|
||||
|
||||
private:
|
||||
ZipPrivate* d;
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Zip::CompressionOptions)
|
||||
|
||||
OSDAB_END_NAMESPACE
|
||||
|
||||
#endif // OSDAB_ZIP__H
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
/****************************************************************************
|
||||
** Filename: zip_p.h
|
||||
** Last updated [dd/mm/yyyy]: 27/03/2011
|
||||
**
|
||||
** pkzip 2.0 file compression.
|
||||
**
|
||||
** Some of the code has been inspired by other open source projects,
|
||||
** (mainly Info-Zip and Gilles Vollant's minizip).
|
||||
** Compression and decompression actually uses the zlib library.
|
||||
**
|
||||
** Copyright (C) 2007-2012 Angius Fabrizio. All rights reserved.
|
||||
**
|
||||
** This file is part of the OSDaB project (http://osdab.42cows.org/).
|
||||
**
|
||||
** This file may be distributed and/or modified under the terms of the
|
||||
** GNU General Public License version 2 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file.
|
||||
**
|
||||
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
||||
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
**
|
||||
** See the file LICENSE.GPL that came with this software distribution or
|
||||
** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information.
|
||||
**
|
||||
**********************************************************************/
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Zip/UnZip API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#ifndef OSDAB_ZIP_P__H
|
||||
#define OSDAB_ZIP_P__H
|
||||
|
||||
#include "zip.h"
|
||||
#include "zipentry_p.h"
|
||||
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QtGlobal>
|
||||
|
||||
#include <zlib/zconf.h>
|
||||
|
||||
/*!
|
||||
zLib authors suggest using larger buffers (128K or 256K) for (de)compression (especially for inflate())
|
||||
we use a 256K buffer here - if you want to use this code on a pre-iceage mainframe please change it ;)
|
||||
*/
|
||||
#define ZIP_READ_BUFFER (256*1024)
|
||||
|
||||
OSDAB_BEGIN_NAMESPACE(Zip)
|
||||
|
||||
class ZipPrivate : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// uLongf from zconf.h
|
||||
typedef uLongf crc_t;
|
||||
|
||||
ZipPrivate();
|
||||
virtual ~ZipPrivate();
|
||||
|
||||
QMap<QString,ZipEntryP*>* headers;
|
||||
|
||||
QIODevice* device;
|
||||
QFile* file;
|
||||
|
||||
char buffer1[ZIP_READ_BUFFER];
|
||||
char buffer2[ZIP_READ_BUFFER];
|
||||
|
||||
unsigned char* uBuffer;
|
||||
|
||||
const crc_t* crcTable;
|
||||
|
||||
QString comment;
|
||||
QString password;
|
||||
|
||||
Zip::ErrorCode createArchive(QIODevice* device);
|
||||
Zip::ErrorCode closeArchive();
|
||||
void reset();
|
||||
|
||||
bool zLibInit();
|
||||
|
||||
bool containsEntry(const QFileInfo& info) const;
|
||||
|
||||
Zip::ErrorCode addDirectory(const QString& path, const QString& root,
|
||||
Zip::CompressionOptions options, Zip::CompressionLevel level,
|
||||
int hierarchyLevel, int* addedFiles = 0);
|
||||
Zip::ErrorCode addFiles(const QStringList& paths, const QString& root,
|
||||
Zip::CompressionOptions options, Zip::CompressionLevel level,
|
||||
int* addedFiles);
|
||||
|
||||
Zip::ErrorCode createEntry(const QFileInfo& file, const QString& root,
|
||||
Zip::CompressionLevel level);
|
||||
Zip::CompressionLevel detectCompressionByMime(const QString& ext);
|
||||
|
||||
inline quint32 updateChecksum(const quint32& crc, const quint32& val) const;
|
||||
|
||||
inline void encryptBytes(quint32* keys, char* buffer, qint64 read);
|
||||
|
||||
inline void setULong(quint32 v, char* buffer, unsigned int offset);
|
||||
inline void updateKeys(quint32* keys, int c) const;
|
||||
inline void initKeys(quint32* keys) const;
|
||||
inline int decryptByte(quint32 key2) const;
|
||||
|
||||
inline QString extractRoot(const QString& p, Zip::CompressionOptions o);
|
||||
|
||||
private slots:
|
||||
void deviceDestroyed(QObject*);
|
||||
|
||||
private:
|
||||
int compressionStrategy(const QString& path, QIODevice& file) const;
|
||||
Zip::ErrorCode deflateFile(const QFileInfo& fileInfo,
|
||||
quint32& crc, qint64& written, const Zip::CompressionLevel& level, quint32** keys);
|
||||
Zip::ErrorCode storeFile(const QString& path, QIODevice& file,
|
||||
quint32& crc, qint64& written, quint32** keys);
|
||||
Zip::ErrorCode compressFile(const QString& path, QIODevice& file,
|
||||
quint32& crc, qint64& written, const Zip::CompressionLevel& level, quint32** keys);
|
||||
Zip::ErrorCode do_closeArchive();
|
||||
Zip::ErrorCode writeEntry(const QString& fileName, const ZipEntryP* h, quint32& szCentralDir);
|
||||
Zip::ErrorCode writeCentralDir(quint32 offCentralDir, quint32 szCentralDir);
|
||||
};
|
||||
|
||||
OSDAB_END_NAMESPACE
|
||||
|
||||
#endif // OSDAB_ZIP_P__H
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
/****************************************************************************
|
||||
** Filename: ZipEntryP.h
|
||||
** Last updated [dd/mm/yyyy]: 27/03/2011
|
||||
**
|
||||
** Wrapper for a ZIP local header.
|
||||
**
|
||||
** Some of the code has been inspired by other open source projects,
|
||||
** (mainly Info-Zip and Gilles Vollant's minizip).
|
||||
** Compression and decompression actually uses the zlib library.
|
||||
**
|
||||
** Copyright (C) 2007-2012 Angius Fabrizio. All rights reserved.
|
||||
**
|
||||
** This file is part of the OSDaB project (http://osdab.42cows.org/).
|
||||
**
|
||||
** This file may be distributed and/or modified under the terms of the
|
||||
** GNU General Public License version 2 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file.
|
||||
**
|
||||
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
||||
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
**
|
||||
** See the file LICENSE.GPL that came with this software distribution or
|
||||
** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information.
|
||||
**
|
||||
**********************************************************************/
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Zip/UnZip API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#ifndef OSDAB_ZIPENTRY_P__H
|
||||
#define OSDAB_ZIPENTRY_P__H
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QtGlobal>
|
||||
|
||||
OSDAB_BEGIN_NAMESPACE(Zip)
|
||||
|
||||
class ZipEntryP
|
||||
{
|
||||
public:
|
||||
ZipEntryP() :
|
||||
lhOffset(0),
|
||||
dataOffset(0),
|
||||
gpFlag(),
|
||||
compMethod(0),
|
||||
modTime(),
|
||||
modDate(),
|
||||
crc(0),
|
||||
szComp(0),
|
||||
szUncomp(0),
|
||||
absolutePath(),
|
||||
fileSize(0),
|
||||
lhEntryChecked(false)
|
||||
{
|
||||
gpFlag[0] = gpFlag[1] = 0;
|
||||
modTime[0] = modTime[1] = 0;
|
||||
modDate[0] = modDate[1] = 0;
|
||||
}
|
||||
|
||||
quint32 lhOffset; // Offset of the local header record for this entry
|
||||
mutable quint32 dataOffset; // Offset of the file data for this entry
|
||||
unsigned char gpFlag[2]; // General purpose flag
|
||||
quint16 compMethod; // Compression method
|
||||
unsigned char modTime[2]; // Last modified time
|
||||
unsigned char modDate[2]; // Last modified date
|
||||
quint32 crc; // CRC32
|
||||
quint32 szComp; // Compressed file size
|
||||
quint32 szUncomp; // Uncompressed file size
|
||||
QString comment; // File comment
|
||||
|
||||
QString absolutePath; // Internal use
|
||||
qint64 fileSize; // Internal use
|
||||
|
||||
mutable bool lhEntryChecked; // Is true if the local header record for this entry has been parsed
|
||||
|
||||
inline bool isEncrypted() const { return gpFlag[0] & 0x01; }
|
||||
inline bool hasDataDescriptor() const { return gpFlag[0] & 0x08; }
|
||||
};
|
||||
|
||||
OSDAB_END_NAMESPACE
|
||||
|
||||
#endif // OSDAB_ZIPENTRY_P__H
|
||||
@@ -0,0 +1,150 @@
|
||||
/****************************************************************************
|
||||
** Filename: zipglobal.cpp
|
||||
** Last updated [dd/mm/yyyy]: 06/02/2011
|
||||
**
|
||||
** pkzip 2.0 file compression.
|
||||
**
|
||||
** Some of the code has been inspired by other open source projects,
|
||||
** (mainly Info-Zip and Gilles Vollant's minizip).
|
||||
** Compression and decompression actually uses the zlib library.
|
||||
**
|
||||
** Copyright (C) 2007-2012 Angius Fabrizio. All rights reserved.
|
||||
**
|
||||
** This file is part of the OSDaB project (http://osdab.42cows.org/).
|
||||
**
|
||||
** This file may be distributed and/or modified under the terms of the
|
||||
** GNU General Public License version 2 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file.
|
||||
**
|
||||
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
||||
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
**
|
||||
** See the file LICENSE.GPL that came with this software distribution or
|
||||
** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information.
|
||||
**
|
||||
**********************************************************************/
|
||||
|
||||
#include "zipglobal.h"
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_WINCE) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
#define OSDAB_ZIP_HAS_UTC
|
||||
#include <ctime>
|
||||
#else
|
||||
#undef OSDAB_ZIP_HAS_UTC
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <QtCore/qt_windows.h>
|
||||
#elif defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
#include <utime.h>
|
||||
#endif
|
||||
|
||||
OSDAB_BEGIN_NAMESPACE(Zip)
|
||||
|
||||
/*! Returns the current UTC offset in seconds unless OSDAB_ZIP_NO_UTC is defined
|
||||
and method is implemented for the current platform and 0 otherwise.
|
||||
*/
|
||||
int OSDAB_ZIP_MANGLE(currentUtcOffset)()
|
||||
{
|
||||
#if !(!defined OSDAB_ZIP_NO_UTC && defined OSDAB_ZIP_HAS_UTC)
|
||||
return 0;
|
||||
#else
|
||||
time_t curr_time_t;
|
||||
time(&curr_time_t);
|
||||
|
||||
#if defined Q_OS_WIN
|
||||
struct tm _tm_struct;
|
||||
struct tm *tm_struct = &_tm_struct;
|
||||
#else
|
||||
struct tm *tm_struct = 0;
|
||||
#endif
|
||||
|
||||
#if !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS)
|
||||
// use the reentrant version of localtime() where available
|
||||
tzset();
|
||||
tm res;
|
||||
tm_struct = gmtime_r(&curr_time_t, &res);
|
||||
#elif defined Q_OS_WIN && !defined Q_CC_MINGW
|
||||
if (gmtime_s(tm_struct, &curr_time_t))
|
||||
return 0;
|
||||
#else
|
||||
tm_struct = gmtime(&curr_time_t);
|
||||
#endif
|
||||
|
||||
if (!tm_struct)
|
||||
return 0;
|
||||
|
||||
const time_t global_time_t = mktime(tm_struct);
|
||||
|
||||
#if !defined(QT_NO_THREAD) && defined(_POSIX_THREAD_SAFE_FUNCTIONS)
|
||||
// use the reentrant version of localtime() where available
|
||||
tm_struct = localtime_r(&curr_time_t, &res);
|
||||
#elif defined Q_OS_WIN && !defined Q_CC_MINGW
|
||||
if (localtime_s(tm_struct, &curr_time_t))
|
||||
return 0;
|
||||
#else
|
||||
tm_struct = localtime(&curr_time_t);
|
||||
#endif
|
||||
|
||||
if (!tm_struct)
|
||||
return 0;
|
||||
|
||||
const time_t local_time_t = mktime(tm_struct);
|
||||
|
||||
const int utcOffset = -qRound(difftime(global_time_t, local_time_t));
|
||||
return tm_struct->tm_isdst > 0 ? utcOffset + 3600 : utcOffset;
|
||||
#endif // No UTC
|
||||
}
|
||||
|
||||
QDateTime OSDAB_ZIP_MANGLE(fromFileTimestamp)(const QDateTime &dateTime)
|
||||
{
|
||||
#if !defined OSDAB_ZIP_NO_UTC && defined OSDAB_ZIP_HAS_UTC
|
||||
const int utc = OSDAB_ZIP_MANGLE(currentUtcOffset)();
|
||||
return dateTime.toUTC().addSecs(utc);
|
||||
#else
|
||||
return dateTime;
|
||||
#endif // OSDAB_ZIP_NO_UTC
|
||||
}
|
||||
|
||||
bool OSDAB_ZIP_MANGLE(setFileTimestamp)(const QString &fileName, const QDateTime &dateTime)
|
||||
{
|
||||
if (fileName.isEmpty())
|
||||
return true;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
HANDLE hFile =
|
||||
CreateFileW(fileName.toStdWString().c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SYSTEMTIME st;
|
||||
FILETIME ft, ftLastMod;
|
||||
const QDate date = dateTime.date();
|
||||
const QTime time = dateTime.time();
|
||||
st.wYear = date.year();
|
||||
st.wMonth = date.month();
|
||||
st.wDay = date.day();
|
||||
st.wHour = time.hour();
|
||||
st.wMinute = time.minute();
|
||||
st.wSecond = time.second();
|
||||
st.wMilliseconds = time.msec();
|
||||
|
||||
SystemTimeToFileTime(&st, &ft);
|
||||
LocalFileTimeToFileTime(&ft, &ftLastMod);
|
||||
|
||||
const bool success = SetFileTime(hFile, NULL, NULL, &ftLastMod);
|
||||
CloseHandle(hFile);
|
||||
return success;
|
||||
|
||||
#elif defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
|
||||
struct utimbuf t_buffer;
|
||||
t_buffer.actime = t_buffer.modtime = dateTime.toSecsSinceEpoch();
|
||||
return utime(fileName.toLocal8Bit().constData(), &t_buffer) == 0;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
OSDAB_END_NAMESPACE
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
/****************************************************************************
|
||||
** Filename: zipglobal.h
|
||||
** Last updated [dd/mm/yyyy]: 27/03/2011
|
||||
**
|
||||
** pkzip 2.0 file compression.
|
||||
**
|
||||
** Some of the code has been inspired by other open source projects,
|
||||
** (mainly Info-Zip and Gilles Vollant's minizip).
|
||||
** Compression and decompression actually uses the zlib library.
|
||||
**
|
||||
** Copyright (C) 2007-2012 Angius Fabrizio. All rights reserved.
|
||||
**
|
||||
** This file is part of the OSDaB project (http://osdab.42cows.org/).
|
||||
**
|
||||
** This file may be distributed and/or modified under the terms of the
|
||||
** GNU General Public License version 2 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL included in the
|
||||
** packaging of this file.
|
||||
**
|
||||
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
||||
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
**
|
||||
** See the file LICENSE.GPL that came with this software distribution or
|
||||
** visit http://www.gnu.org/copyleft/gpl.html for GPL licensing information.
|
||||
**
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef OSDAB_ZIPGLOBAL__H
|
||||
#define OSDAB_ZIPGLOBAL__H
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QtGlobal>
|
||||
|
||||
/* If you want to build the OSDaB Zip code as
|
||||
a library, define OSDAB_ZIP_LIB in the library's .pro file and
|
||||
in the libraries using it OR remove the #ifndef OSDAB_ZIP_LIB
|
||||
define below and leave the #else body. Also remember to define
|
||||
OSDAB_ZIP_BUILD_LIB in the library's project).
|
||||
*/
|
||||
|
||||
#ifndef OSDAB_ZIP_LIB
|
||||
# define OSDAB_ZIP_EXPORT
|
||||
#else
|
||||
# if defined(OSDAB_ZIP_BUILD_LIB)
|
||||
# define OSDAB_ZIP_EXPORT Q_DECL_EXPORT
|
||||
# else
|
||||
# define OSDAB_ZIP_EXPORT Q_DECL_IMPORT
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef OSDAB_NAMESPACE
|
||||
#define OSDAB_BEGIN_NAMESPACE(ModuleName) namespace Osdab { namespace ModuleName {
|
||||
#else
|
||||
#define OSDAB_BEGIN_NAMESPACE(ModuleName)
|
||||
#endif
|
||||
|
||||
#ifdef OSDAB_NAMESPACE
|
||||
#define OSDAB_END_NAMESPACE } }
|
||||
#else
|
||||
#define OSDAB_END_NAMESPACE
|
||||
#endif
|
||||
|
||||
#ifndef OSDAB_NAMESPACE
|
||||
#define OSDAB_ZIP_MANGLE(x) zip_##x
|
||||
#else
|
||||
#define OSDAB_ZIP_MANGLE(x) x
|
||||
#endif
|
||||
|
||||
OSDAB_BEGIN_NAMESPACE(Zip)
|
||||
|
||||
OSDAB_ZIP_EXPORT int OSDAB_ZIP_MANGLE(currentUtcOffset)();
|
||||
OSDAB_ZIP_EXPORT QDateTime OSDAB_ZIP_MANGLE(fromFileTimestamp)(const QDateTime& dateTime);
|
||||
OSDAB_ZIP_EXPORT bool OSDAB_ZIP_MANGLE(setFileTimestamp)(const QString& fileName, const QDateTime& dateTime);
|
||||
|
||||
OSDAB_END_NAMESPACE
|
||||
|
||||
#endif // OSDAB_ZIPGLOBAL__H
|
||||
Reference in New Issue
Block a user