This commit is contained in:
BruebachL 2026-03-18 02:15:49 -07:00 committed by GitHub
commit 84889e8ca4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 5859 additions and 1 deletions

View File

@ -35,6 +35,7 @@ set(cockatrice_SOURCES
src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp
src/interface/widgets/dialogs/dlg_forgot_password_request.cpp
src/interface/widgets/dialogs/dlg_forgot_password_reset.cpp
src/interface/widgets/dialogs/dlg_import_precons.cpp
src/interface/widgets/dialogs/dlg_load_deck.cpp
src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp
src/interface/widgets/dialogs/dlg_load_deck_from_website.cpp
@ -366,6 +367,30 @@ if(APPLE)
set(cockatrice_SOURCES ${cockatrice_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns)
endif(APPLE)
# Libz is required to support zipped files
find_package(ZLIB)
if(ZLIB_FOUND)
include_directories(${ZLIB_INCLUDE_DIRS})
add_definitions("-DHAS_ZLIB")
set(cockatrice_SOURCES ${cockatrice_SOURCES} src/utility/external/zip/unzip.cpp
src/utility/external/zip/zipglobal.cpp
)
else()
message(STATUS "Cockatrice: zlib not found; ZIP support disabled")
endif()
# LibLZMA is required to support xz files
find_package(LibLZMA)
if(LIBLZMA_FOUND)
include_directories(${LIBLZMA_INCLUDE_DIRS})
add_definitions("-DHAS_LZMA")
set(cockatrice_SOURCES ${cockatrice_SOURCES} src/utility/external/lzma/decompress.cpp)
else()
message(STATUS "Cockatrice: LibLZMA not found; xz support disabled")
endif()
if(Qt6_FOUND)
qt6_add_resources(cockatrice_RESOURCES_RCC ${cockatrice_RESOURCES})
elseif(Qt5_FOUND)
@ -448,6 +473,14 @@ else()
)
endif()
if(ZLIB_FOUND)
target_link_libraries(cockatrice PRIVATE ${ZLIB_LIBRARIES})
endif()
if(LIBLZMA_FOUND)
target_link_libraries(cockatrice PRIVATE ${LIBLZMA_LIBRARIES})
endif()
if(UNIX)
if(APPLE)
set(MACOSX_BUNDLE_INFO_STRING "${PROJECT_NAME}")

View File

@ -0,0 +1,684 @@
#include "dlg_import_precons.h"
#include "../../deck_loader/deck_loader.h"
#include "../settings/cache_settings.h"
#include "libcockatrice/deck_list/deck_list_card_node.h"
#include <QDebug>
#include <QFileDialog>
#include <QGridLayout>
#include <QJsonDocument>
#include <QMessageBox>
#include <QNetworkReply>
#include <QPushButton>
#include <QTemporaryDir>
#include <libcockatrice/deck_list/deck_list.h>
#ifdef HAS_LZMA
#include "../../src/utility/external/lzma/decompress.h"
#endif
#ifdef HAS_ZLIB
#include "../../src/utility/external/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 MTGJSON_VERSION_URL "https://www.mtgjson.com/api/v5/Meta.json"
#define ALLDECKS_URL_FALLBACK "https://mtgjson.com/api/v5/AllDeckFiles.zip"
#ifdef HAS_LZMA
#define ALLDECKS_URL "https://mtgjson.com/api/v5/AllDeckFiles.tar.xz"
#elif defined(HAS_ZLIB)
#define ALLDECKS_URL "https://mtgjson.com/api/v5/AllDeckFiles.zip"
#else
#define ALLDECKS_URL ""
#endif
DlgImportPrecons::DlgImportPrecons(QWidget *parent) : QWizard(parent)
{
// define a dummy context that will be used where needed
QString dummy = QT_TRANSLATE_NOOP("i18n", "English");
nam = new QNetworkAccessManager(this);
addPage(new LoadPreconsPage);
addPage(new SavePreconsPage);
retranslateUi();
}
void DlgImportPrecons::retranslateUi()
{
setWindowTitle(tr("Preconstructed Deck Importer"));
}
void DlgImportPrecons::accept()
{
QDialog::accept();
}
void DlgImportPrecons::enableButtons()
{
button(QWizard::NextButton)->setDisabled(false);
button(QWizard::BackButton)->setDisabled(false);
}
void DlgImportPrecons::disableButtons()
{
button(QWizard::NextButton)->setDisabled(true);
button(QWizard::BackButton)->setDisabled(true);
}
LoadPreconsPage::LoadPreconsPage(QWidget *parent) : QWizardPage(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, &LoadPreconsPage::actRestoreDefaultUrl);
fileButton = new QPushButton(this);
connect(fileButton, &QPushButton::clicked, this, &LoadPreconsPage::actLoadPreconsFile);
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, &LoadPreconsPage::importFinished);
setLayout(layout);
retranslateUi();
}
void LoadPreconsPage::initializePage()
{
urlLineEdit->setText(ALLDECKS_URL);
progressLabel->hide();
progressBar->hide();
}
void LoadPreconsPage::retranslateUi()
{
setTitle(tr("Source selection"));
setSubTitle(tr("Please specify a compatible source for the list of preconstructed Decks. "
"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 LoadPreconsPage::actRestoreDefaultUrl()
{
urlLineEdit->setText(ALLDECKS_URL);
}
void LoadPreconsPage::actLoadPreconsFile()
{
QFileDialog dialog(this, tr("Load preconstructed Deck file"));
dialog.setFileMode(QFileDialog::ExistingFile);
QString extensions = "*.json *.xml";
#ifdef HAS_ZLIB
extensions += " *.zip";
#endif
#ifdef HAS_LZMA
extensions += " *.xz";
#endif
dialog.setNameFilter(tr("Precons 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 LoadPreconsPage::validatePage()
{
// once the import is finished, we call next(); skip validation
if (dynamic_cast<DlgImportPrecons *>(wizard())->doneWithParsing) {
return true;
}
if (urlRadioButton->isChecked()) {
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();
dynamic_cast<DlgImportPrecons *>(wizard())->disableButtons();
setEnabled(false);
downloadPreconsFile(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;
}
dynamic_cast<DlgImportPrecons *>(wizard())->disableButtons();
setEnabled(false);
dynamic_cast<DlgImportPrecons *>(wizard())->setCardSourceUrl(setsFile.fileName());
readPreconsFromByteArray(setsFile.readAll());
}
return false;
}
void LoadPreconsPage::downloadPreconsFile(const QUrl &url)
{
const auto urlString = url.toString();
if (urlString == ALLDECKS_URL || urlString == ALLDECKS_URL_FALLBACK) {
const auto versionUrl = QUrl::fromUserInput(MTGJSON_VERSION_URL);
auto *versionReply = dynamic_cast<DlgImportPrecons *>(wizard())->nam->get(QNetworkRequest(versionUrl));
connect(versionReply, &QNetworkReply::finished, [versionReply]() {
if (versionReply->error() == QNetworkReply::NoError) {
auto jsonData = versionReply->readAll();
QJsonParseError jsonError{};
auto jsonResponse = QJsonDocument::fromJson(jsonData, &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";
}
}
}
versionReply->deleteLater();
});
}
dynamic_cast<DlgImportPrecons *>(wizard())->setCardSourceUrl(url.toString());
auto *reply = dynamic_cast<DlgImportPrecons *>(wizard())->nam->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this, &LoadPreconsPage::actDownloadFinishedPreconsFile);
connect(reply, &QNetworkReply::downloadProgress, this, &LoadPreconsPage::actDownloadProgressPreconsFile);
}
void LoadPreconsPage::actDownloadProgressPreconsFile(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 LoadPreconsPage::actDownloadFinishedPreconsFile()
{
// 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()));
dynamic_cast<DlgImportPrecons *>(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();
downloadPreconsFile(redirectUrl);
reply->deleteLater();
return;
}
progressLabel->hide();
progressBar->hide();
readPreconsFromByteArray(reply->readAll());
reply->deleteLater();
}
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
bool LoadPreconsPage::parsePreconsFromByteArray(const QByteArray &data, QString folderPath)
{
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
if (parseError.error != QJsonParseError::NoError || !doc.isObject()) {
qWarning() << "JSON parse error:" << parseError.errorString();
return false;
}
QJsonObject rootObj = doc.object();
QJsonObject preconData = rootObj.value("data").toObject();
QString deckName = preconData.value("name").toString();
QString shortName = preconData.value("code").toString().toUpper();
QString deckType = preconData.value("type").toString();
QJsonArray mainBoard = preconData.value("mainBoard").toArray();
int releaseYear = preconData.value("releaseDate").toString().split("-").at(0).toInt();
qInfo() << "Importing '" << deckName << "' from" << shortName;
auto *precon = new DeckLoader(this);
for (const auto &cardVal : mainBoard) {
QJsonObject cardObj = cardVal.toObject();
QString name = cardObj.value("name").toString();
QString setCode = cardObj.value("setCode").toString();
QString number = cardObj.value("number").toString();
int count = cardObj.value("count").toInt();
QString scryfallId = cardObj.value("identifiers").toObject().value("scryfallId").toString();
DecklistCardNode *addedCard = precon->getDeckList()->addCard(name, "main", -1, setCode, number, scryfallId);
if (count != 1) {
addedCard->setNumber(count);
}
}
precon->getDeckList()->setName(deckName);
QJsonArray commanderArray = preconData.value("commander").toArray();
if (!commanderArray.isEmpty()) {
QJsonObject commanderObj = commanderArray.first().toObject();
QString commanderName = commanderObj.value("name").toString();
QString commanderId = commanderObj.value("identifiers").toObject().value("scryfallId").toString();
precon->getDeckList()->setBannerCard({commanderName, commanderId});
} else {
qInfo() << "No commander data found.";
}
QString dirPath = QDir::cleanPath(folderPath + QDir::separator() + deckType + QDir::separator() +
QString::number(releaseYear) + QDir::separator() + shortName);
QString fullPath = QDir(dirPath).filePath(precon->getDeckList()->getName());
QDir dir;
if (!dir.exists(dirPath)) {
if (!dir.mkpath(dirPath)) {
qWarning() << "Failed to create directory:" << dirPath;
return false;
}
}
if (precon->getDeckList()->getCardList().length() > 1) {
precon->saveToFile(fullPath + ".cod", DeckLoader::CockatriceFormat);
}
return true;
}
void LoadPreconsPage::readPreconsFromByteArray(QByteArray _data)
{
progressBar->setMaximum(0);
progressBar->setMinimum(0);
progressBar->setValue(0);
progressLabel->setText(tr("Parsing file"));
progressLabel->show();
progressBar->show();
dynamic_cast<DlgImportPrecons *>(wizard())->doneWithParsing = false;
dynamic_cast<DlgImportPrecons *>(wizard())->xmlData.clear();
readPreconsFromByteArrayRef(_data);
}
QString LoadPreconsPage::createTmpDirectory()
{
QTemporaryDir tempDir;
if (tempDir.isValid()) {
return tempDir.path();
}
QString tmpPath = QDir::cleanPath(SettingsCache::instance().getDeckPath() + "/Precons/tmp");
QDir tmpDir(tmpPath);
if (!tmpDir.exists()) {
if (!QDir().mkpath(tmpPath)) {
qWarning() << "Failed to create temporary directory.";
return "";
}
return tmpPath;
}
return tmpPath;
}
void LoadPreconsPage::processTarArchive(const QByteArray &tarData)
{
const int blockSize = 512;
int offset = 0;
dynamic_cast<DlgImportPrecons *>(wizard())->setTempDir(createTmpDirectory());
while (offset + blockSize <= tarData.size()) {
QByteArray header = tarData.mid(offset, blockSize);
QString fileName = QString::fromLatin1(header.left(100).trimmed());
if (fileName.isEmpty())
break;
QByteArray sizeField = header.mid(124, 12).trimmed();
bool ok = false;
int fileSize = sizeField.toInt(&ok, 8);
if (!ok || fileSize < 0)
break;
int fileStart = offset + blockSize;
QByteArray fileContents = tarData.mid(fileStart, fileSize);
parsePreconsFromByteArray(fileContents, dynamic_cast<DlgImportPrecons *>(wizard())->getTempDir());
offset = fileStart + ((fileSize + blockSize - 1) / blockSize) * blockSize;
}
dynamic_cast<DlgImportPrecons *>(wizard())->doneWithParsing = true;
importFinished();
}
void LoadPreconsPage::readPreconsFromByteArrayRef(QByteArray &_data)
{
// XZ-compressed TAR archive
if (_data.startsWith(XZ_SIGNATURE)) {
#ifdef HAS_LZMA
qInfo() << "Unzipping precon tar.xz file";
auto *inBuffer = new QBuffer(&_data);
QByteArray tarData;
auto *outBuffer = new QBuffer(&tarData);
inBuffer->open(QBuffer::ReadOnly);
outBuffer->open(QBuffer::WriteOnly);
XzDecompressor xz;
if (!xz.decompress(inBuffer, outBuffer)) {
zipDownloadFailed(tr("Xz extraction failed."));
return;
}
_data.clear();
processTarArchive(tarData);
return;
#else
zipDownloadFailed(tr("Sorry, your computer does not support xz compressed files."));
static_cast<DlgImportPrecons *>(wizard())->enableButtons();
setEnabled(true);
progressLabel->hide();
progressBar->hide();
return;
#endif
}
// ZIP archive
else if (_data.startsWith(ZIP_SIGNATURE)) {
#ifdef HAS_ZLIB
qInfo() << "Unzipping precon ZIP file";
QBuffer inBuffer(&_data);
UnZip uz;
UnZip::ErrorCode ec = uz.openArchive(&inBuffer);
if (ec != UnZip::Ok) {
zipDownloadFailed(tr("Failed to open Zip archive: %1.").arg(uz.formatError(ec)));
return;
}
const QStringList files = uz.fileList();
if (files.isEmpty()) {
zipDownloadFailed(tr("Zip extraction failed: the Zip archive is empty."));
return;
}
for (const QString &fileName : files) {
if (!fileName.endsWith(".json", Qt::CaseInsensitive))
continue;
QBuffer *outBuffer = new QBuffer();
outBuffer->open(QIODevice::ReadWrite);
ec = uz.extractFile(fileName, outBuffer);
if (ec != UnZip::Ok) {
zipDownloadFailed(tr("Zip extraction failed for file %1: %2").arg(fileName, uz.formatError(ec)));
uz.closeArchive();
return;
}
outBuffer->seek(0);
delete outBuffer;
}
uz.closeArchive();
importFinished(); // Continue processing
return;
#else
zipDownloadFailed(tr("Sorry, your computer does not support zipped files."));
static_cast<DlgImportPrecons *>(wizard())->enableButtons();
setEnabled(true);
progressLabel->hide();
progressBar->hide();
return;
#endif
}
// Raw JSON content
else if (_data.startsWith("{")) {
jsonData = std::move(_data);
watcher.setFuture(future);
}
// XML content
else if (_data.startsWith("<")) {
dynamic_cast<DlgImportPrecons *>(wizard())->doneWithParsing = true;
dynamic_cast<DlgImportPrecons *>(wizard())->xmlData = std::move(_data);
importFinished();
}
// Unknown format
else {
static_cast<DlgImportPrecons *>(wizard())->enableButtons();
setEnabled(true);
progressLabel->hide();
progressBar->hide();
QMessageBox::critical(this, tr("Error"), tr("Failed to interpret downloaded data."));
}
}
void LoadPreconsPage::zipDownloadFailed(const QString &message)
{
dynamic_cast<DlgImportPrecons *>(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(ALLDECKS_URL_FALLBACK);
wizard()->next();
}
}
void LoadPreconsPage::importFinished()
{
dynamic_cast<DlgImportPrecons *>(wizard())->enableButtons();
setEnabled(true);
progressLabel->hide();
progressBar->hide();
if (dynamic_cast<DlgImportPrecons *>(wizard())->doneWithParsing || watcher.future().result()) {
wizard()->next();
} else {
QMessageBox::critical(
this, tr("Error"),
tr("The file was retrieved successfully, but it does not contain any preconstructed decks data."));
}
}
SavePreconsPage::SavePreconsPage(QWidget *parent) : QWizardPage(parent)
{
saveLabel = new QLabel(this);
folderTreeWidget = new QTreeWidget(this);
folderTreeWidget->setHeaderHidden(true);
folderTreeWidget->setSelectionMode(QAbstractItemView::NoSelection);
folderTreeWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
auto *layout = new QGridLayout(this);
layout->addWidget(saveLabel, 0, 0);
layout->addWidget(folderTreeWidget, 1, 0);
setLayout(layout);
retranslateUi();
}
void SavePreconsPage::cleanupPage()
{
}
void SavePreconsPage::initializePage()
{
QDir tempDir(dynamic_cast<DlgImportPrecons *>(wizard())->getTempDir());
folderTreeWidget->clear();
for (const QString &dirName : tempDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
QString absPath = tempDir.absoluteFilePath(dirName);
QTreeWidgetItem *item = new QTreeWidgetItem(folderTreeWidget, QStringList() << dirName);
item->setData(0, Qt::UserRole, absPath);
item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate);
item->setCheckState(0, Qt::Unchecked);
populateFolderTree(item, absPath);
}
retranslateUi();
}
void SavePreconsPage::populateFolderTree(QTreeWidgetItem *parent, const QString &path)
{
QDir dir(path);
for (const QString &subdir : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
QString absPath = dir.absoluteFilePath(subdir);
QTreeWidgetItem *child = new QTreeWidgetItem(parent, QStringList() << subdir);
child->setData(0, Qt::UserRole, absPath);
child->setFlags(child->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate);
child->setCheckState(0, Qt::Unchecked);
populateFolderTree(child, absPath);
}
}
void SavePreconsPage::retranslateUi()
{
setTitle(tr("Precons imported"));
setSubTitle(tr("The following preconstructed deck types have been found:"));
saveLabel->setText(tr("Select the product types you'd like to import and then press \"Save\" to store the imported "
"preconstructed decks in your deck folder. \n (Note: It is not recommended to import all "
"products unless you are sure your computer can handle it. \n It might cause Cockatrice to "
"load a very large amount of decks when using the visual deck storage"));
setButtonText(QWizard::NextButton, tr("&Save"));
}
bool SavePreconsPage::validatePage()
{
for (int i = 0; i < folderTreeWidget->topLevelItemCount(); ++i) {
QTreeWidgetItem *item = folderTreeWidget->topLevelItem(i);
copyCheckedFolders(item);
}
QDir(dynamic_cast<DlgImportPrecons *>(wizard())->getTempDir()).removeRecursively();
return true;
}
void SavePreconsPage::copyCheckedFolders(QTreeWidgetItem *item)
{
Qt::CheckState state = item->checkState(0);
if (state == Qt::Unchecked)
return;
QString srcPath = item->data(0, Qt::UserRole).toString();
QString relativePath = QDir(dynamic_cast<DlgImportPrecons *>(wizard())->getTempDir()).relativeFilePath(srcPath);
QString destPath = QDir::cleanPath(SettingsCache::instance().getDeckPath() + QDir::separator() + "Precons" +
QDir::separator() + relativePath);
if (!copyDirectory(srcPath, destPath))
qWarning() << "Failed to copy" << srcPath;
for (int i = 0; i < item->childCount(); ++i) {
copyCheckedFolders(item->child(i));
}
}
bool SavePreconsPage::copyDirectory(const QString &srcPath, const QString &destPath)
{
QDir srcDir(srcPath);
if (!srcDir.exists())
return false;
QDir destDir;
if (!destDir.mkpath(destPath))
return false;
QFileInfoList entries = srcDir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
for (const QFileInfo &entry : entries) {
QString src = entry.absoluteFilePath();
QString dest = destPath + "/" + entry.fileName();
if (entry.isDir()) {
if (!copyDirectory(src, dest))
return false;
} else {
if (!QFile::copy(src, dest))
return false;
}
}
return true;
}

View File

@ -0,0 +1,116 @@
#ifndef DLG_IMPORT_PRECONS_H
#define DLG_IMPORT_PRECONS_H
#include <QCheckBox>
#include <QFutureWatcher>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QNetworkAccessManager>
#include <QProgressBar>
#include <QRadioButton>
#include <QSettings>
#include <QTextEdit>
#include <QTreeWidget>
#include <QWidget>
#include <QWizardPage>
class DlgImportPrecons : public QWizard
{
Q_OBJECT
public:
explicit DlgImportPrecons(QWidget *parent = nullptr);
void accept() override;
void enableButtons();
void disableButtons();
void retranslateUi();
void setCardSourceUrl(const QString &sourceUrl)
{
cardSourceUrl = sourceUrl;
}
const QString &getCardSourceUrl() const
{
return cardSourceUrl;
}
void setTempDir(const QString &tempDir)
{
tmpDir = tempDir;
}
const QString &getTempDir() const
{
return tmpDir;
}
QNetworkAccessManager *nam;
QByteArray xmlData;
bool doneWithParsing = false;
private:
QString cardSourceUrl;
QString tmpDir;
};
class LoadPreconsPage : public QWizardPage
{
Q_OBJECT
public:
explicit LoadPreconsPage(QWidget *parent = nullptr);
void retranslateUi();
protected:
void initializePage() override;
bool validatePage() override;
bool parsePreconsFromByteArray(const QByteArray &data, QString folderPath);
void readPreconsFromByteArray(QByteArray _data);
static QString createTmpDirectory();
void processTarArchive(const QByteArray &tarData);
void readPreconsFromByteArrayRef(QByteArray &_data);
void downloadPreconsFile(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 actLoadPreconsFile();
void actRestoreDefaultUrl();
void actDownloadProgressPreconsFile(qint64 received, qint64 total);
void actDownloadFinishedPreconsFile();
void importFinished();
void zipDownloadFailed(const QString &message);
};
class SavePreconsPage : public QWizardPage
{
Q_OBJECT
public:
explicit SavePreconsPage(QWidget *parent = nullptr);
void retranslateUi();
private:
QTreeWidget *folderTreeWidget;
QLabel *saveLabel;
protected:
void initializePage() override;
void populateFolderTree(QTreeWidgetItem *parent, const QString &path);
void onItemChanged(QTreeWidgetItem *item, int column);
void cleanupPage() override;
bool validatePage() override;
void copyCheckedFolders(QTreeWidgetItem *item);
bool copyDirectory(const QString &srcPath, const QString &destPath);
};
bool readPreconsFromByteArray(const QByteArray &data);
#endif // DLG_IMPORT_PRECONS_H

View File

@ -40,6 +40,7 @@
#include "../main.h"
#include "logger.h"
#include "version_string.h"
#include "widgets/dialogs/dlg_import_precons.h"
#include "widgets/utility/get_text_with_max.h"
#include <QAction>
@ -691,6 +692,7 @@ void MainWindow::retranslateUi()
aCheckCardUpdates->setText(tr("Check for Card Updates..."));
aCheckCardUpdatesBackground->setText(tr("Check for Card Updates (Automatic)"));
aStatusBar->setText(tr("Show Status Bar"));
aImportPrecons->setText(tr("Import preconstructed Decks..."));
aViewLog->setText(tr("View &Debug Log"));
aOpenSettingsFolder->setText(tr("Open Settings Folder"));
@ -749,6 +751,8 @@ void MainWindow::createActions()
aStatusBar->setCheckable(true);
aStatusBar->setChecked(SettingsCache::instance().getShowStatusBar());
connect(aStatusBar, &QAction::triggered, &SettingsCache::instance(), &SettingsCache::setShowStatusBar);
aImportPrecons = new QAction(this);
connect(aImportPrecons, &QAction::triggered, this, &MainWindow::actImportPrecons);
aViewLog = new QAction(this);
connect(aViewLog, &QAction::triggered, this, &MainWindow::actViewLog);
aOpenSettingsFolder = new QAction(this);
@ -827,6 +831,7 @@ void MainWindow::createMenus()
helpMenu->addAction(aUpdate);
helpMenu->addAction(aCheckCardUpdates);
helpMenu->addAction(aCheckCardUpdatesBackground);
helpMenu->addAction(aImportPrecons);
helpMenu->addSeparator();
helpMenu->addAction(aStatusBar);
helpMenu->addAction(aViewLog);
@ -1360,6 +1365,12 @@ void MainWindow::checkClientUpdatesFinished(bool needToUpdate, bool /* isCompati
}
}
void MainWindow::actImportPrecons()
{
auto preconImporter = new DlgImportPrecons(this);
preconImporter->show();
}
void MainWindow::refreshShortcuts()
{
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();

View File

@ -110,6 +110,7 @@ private slots:
void cardDatabaseAllNewSetsEnabled();
void checkClientUpdatesFinished(bool needToUpdate, bool isCompatible, Release *release);
void actImportPrecons();
void actOpenCustomFolder();
void actOpenCustomsetsFolder();
@ -147,7 +148,7 @@ private:
QAction *aConnect, *aDisconnect, *aRegister, *aForgotPassword, *aSinglePlayer, *aWatchReplay, *aFullScreen;
QAction *aManageSets, *aEditTokens, *aOpenCustomFolder, *aOpenCustomsetsFolder, *aAddCustomSet,
*aReloadCardDatabase;
QAction *aTips, *aUpdate, *aCheckCardUpdates, *aCheckCardUpdatesBackground, *aStatusBar, *aViewLog,
QAction *aTips, *aUpdate, *aCheckCardUpdates, *aCheckCardUpdatesBackground, *aImportPrecons, *aStatusBar, *aViewLog,
*aOpenSettingsFolder;
TabSupervisor *tabSupervisor;

View File

@ -0,0 +1,244 @@
/*
* 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 "decompress.h"
#include <QDebug>
#include <lzma.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;
}
}
}

View File

@ -0,0 +1,20 @@
#ifndef XZ_DECOMPRESS_H
#define XZ_DECOMPRESS_H
#include <QBuffer>
#include <lzma.h>
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

View File

@ -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

View File

@ -0,0 +1,199 @@
/* 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 <QString>
#include <QVariant>
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);
};
} // namespace QtJson
#endif // JSON_H

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -0,0 +1,138 @@
/****************************************************************************
** 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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,160 @@
/****************************************************************************
** 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

View File

@ -0,0 +1,142 @@
/****************************************************************************
** 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

View File

@ -0,0 +1,87 @@
/****************************************************************************
** 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

View File

@ -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

View File

@ -0,0 +1,83 @@
/****************************************************************************
** 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