From 8d3f13c4478f7e99a32a0804d57ac2ece05d7e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Mon, 14 Sep 2015 23:49:32 +0200 Subject: [PATCH] GH-1227 add world copy and rename --- application/pages/WorldListPage.cpp | 43 ++++++- application/pages/WorldListPage.h | 2 + application/pages/WorldListPage.ui | 16 +++ logic/minecraft/World.cpp | 183 ++++++++++++++++++++++------ logic/minecraft/World.h | 9 +- logic/minecraft/WorldList.cpp | 10 +- logic/minecraft/WorldList.h | 3 +- 7 files changed, 220 insertions(+), 46 deletions(-) diff --git a/application/pages/WorldListPage.cpp b/application/pages/WorldListPage.cpp index 30981978..3b531d5d 100644 --- a/application/pages/WorldListPage.cpp +++ b/application/pages/WorldListPage.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "MultiMC.h" @@ -35,6 +36,7 @@ WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr worl ui->setupUi(this); ui->tabWidget->tabBar()->hide(); QSortFilterProxyModel * proxy = new QSortFilterProxyModel(this); + proxy->setSortCaseSensitivity(Qt::CaseInsensitive); proxy->setSourceModel(m_worlds.get()); ui->worldTreeView->setSortingEnabled(true); ui->worldTreeView->setModel(proxy); @@ -207,6 +209,8 @@ void WorldListPage::worldChanged(const QModelIndex ¤t, const QModelIndex & ui->copySeedBtn->setEnabled(enable); ui->mcEditBtn->setEnabled(enable); ui->rmWorldBtn->setEnabled(enable); + ui->copyBtn->setEnabled(enable); + ui->renameBtn->setEnabled(enable); } void WorldListPage::on_addBtn_clicked() @@ -224,4 +228,41 @@ void WorldListPage::on_addBtn_clicked() } m_worlds->startWatching(); } -} \ No newline at end of file +} + +void WorldListPage::on_copyBtn_clicked() +{ + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) + { + return; + } + auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); + auto world = (World *) worldVariant.value(); + bool ok = false; + QString name = QInputDialog::getText(this, tr("World name"), tr("Enter a new name for the copy."), QLineEdit::Normal, world->name(), &ok); + + if (ok && name.length() > 0) + { + world->install(m_worlds->dir().absolutePath(), name); + } +} + +void WorldListPage::on_renameBtn_clicked() +{ + QModelIndex index = getSelectedWorld(); + if (!index.isValid()) + { + return; + } + auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); + auto world = (World *) worldVariant.value(); + + bool ok = false; + QString name = QInputDialog::getText(this, tr("World name"), tr("Enter a new world name."), QLineEdit::Normal, world->name(), &ok); + + if (ok && name.length() > 0) + { + world->rename(name); + } +} diff --git a/application/pages/WorldListPage.h b/application/pages/WorldListPage.h index f3111f93..f0b0e7e9 100644 --- a/application/pages/WorldListPage.h +++ b/application/pages/WorldListPage.h @@ -82,6 +82,8 @@ private slots: void on_mcEditBtn_clicked(); void on_rmWorldBtn_clicked(); void on_addBtn_clicked(); + void on_copyBtn_clicked(); + void on_renameBtn_clicked(); void on_viewFolderBtn_clicked(); void worldChanged(const QModelIndex ¤t, const QModelIndex &previous); }; diff --git a/application/pages/WorldListPage.ui b/application/pages/WorldListPage.ui index ecd9709f..b5fd33da 100644 --- a/application/pages/WorldListPage.ui +++ b/application/pages/WorldListPage.ui @@ -70,6 +70,20 @@ + + + + Copy + + + + + + + Rename + + + @@ -134,6 +148,8 @@ tabWidget worldTreeView addBtn + copyBtn + renameBtn rmWorldBtn mcEditBtn copySeedBtn diff --git a/logic/minecraft/World.cpp b/logic/minecraft/World.cpp index cda81309..41d62b1b 100644 --- a/logic/minecraft/World.cpp +++ b/logic/minecraft/World.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include "World.h" #include @@ -29,6 +30,83 @@ #include #include +std::unique_ptr parseLevelDat(QByteArray data) +{ + QByteArray output; + if(!GZip::unzip(data, output)) + { + return nullptr; + } + std::istringstream foo(std::string(output.constData(), output.size())); + auto pair = nbt::io::read_compound(foo); + + if(pair.first != "") + return nullptr; + + if(pair.second == nullptr) + return nullptr; + + return std::move(pair.second); +} + +QByteArray serializeLevelDat(nbt::tag_compound * levelInfo) +{ + std::ostringstream s; + nbt::io::write_tag("", *levelInfo, s); + QByteArray val( s.str().data(), (int) s.str().size() ); + return val; +} + +QString getLevelDatFromFS(const QFileInfo &file) +{ + QDir worldDir(file.filePath()); + if(!file.isDir() || !worldDir.exists("level.dat")) + { + return QString(); + } + return worldDir.absoluteFilePath("level.dat"); +} + +QByteArray getLevelDatDataFromFS(const QFileInfo &file) +{ + auto fullFilePath = getLevelDatFromFS(file); + if(fullFilePath.isNull()) + { + return QByteArray(); + } + QFile f(fullFilePath); + if(!f.open(QIODevice::ReadOnly)) + { + return QByteArray(); + } + return f.readAll(); +} + +bool putLevelDatDataToFS(const QFileInfo &file, QByteArray & data) +{ + auto fullFilePath = getLevelDatFromFS(file); + if(fullFilePath.isNull()) + { + return false; + } + QSaveFile f(fullFilePath); + if(!f.open(QIODevice::WriteOnly)) + { + return false; + } + QByteArray compressed; + if(!GZip::zip(data, compressed)) + { + return false; + } + if(f.write(compressed) != compressed.size()) + { + f.cancelWriting(); + return false; + } + return f.commit(); +} + World::World(const QFileInfo &file) { repath(file); @@ -50,23 +128,14 @@ void World::repath(const QFileInfo &file) void World::readFromFS(const QFileInfo &file) { - QDir worldDir(file.filePath()); - is_valid = file.isDir() && worldDir.exists("level.dat"); - if(!is_valid) + auto bytes = getLevelDatDataFromFS(file); + if(bytes.isEmpty()) { + is_valid = false; return; } - - auto fullFilePath = worldDir.absoluteFilePath("level.dat"); - QFile f(fullFilePath); - is_valid = f.open(QIODevice::ReadOnly); - if(!is_valid) - { - return; - } - QFileInfo finfo(fullFilePath); - levelDatTime = finfo.lastModified(); - parseLevelDat(f.readAll()); + loadFromLevelDat(bytes); + levelDatTime = file.lastModified(); } void World::readFromZip(const QFileInfo &file) @@ -104,17 +173,18 @@ void World::readFromZip(const QFileInfo &file) { return; } - parseLevelDat(zippedFile.readAll()); + loadFromLevelDat(zippedFile.readAll()); zippedFile.close(); } -bool World::install(QString to) +bool World::install(const QString &to, const QString &name) { auto finalPath = PathCombine(to, DirNameFromString(m_actualName, to)); if(!ensureFolderPathExists(finalPath)) { return false; } + bool ok = false; if(m_containerFile.isFile()) { QuaZip zip(m_containerFile.absoluteFilePath()); @@ -122,14 +192,63 @@ bool World::install(QString to) { return false; } - return !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath).isEmpty(); + ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath).isEmpty(); } else if(m_containerFile.isDir()) { QString from = m_containerFile.filePath(); - return copyPath(from, finalPath); + ok = copyPath(from, finalPath); } - return false; + + if(ok && !name.isEmpty() && m_actualName != name) + { + World newWorld(finalPath); + if(newWorld.isValid()) + { + newWorld.rename(name); + } + } + return ok; +} + +bool World::rename(const QString &newName) +{ + if(m_containerFile.isFile()) + { + return false; + } + + auto data = getLevelDatDataFromFS(m_containerFile); + if(data.isEmpty()) + { + return false; + } + + auto worldData = parseLevelDat(data); + if(!worldData) + { + return false; + } + auto &val = worldData->at("Data"); + if(val.get_type() != nbt::tag_type::Compound) + { + return false; + } + auto &dataCompound = val.as(); + dataCompound.put("LevelName", nbt::value_initializer(newName.toUtf8().data())); + data = serializeLevelDat(worldData.get()); + + putLevelDatDataToFS(m_containerFile, data); + + m_actualName = newName; + + QDir parentDir(m_containerFile.absoluteFilePath()); + parentDir.cdUp(); + QFile container(m_containerFile.absoluteFilePath()); + auto dirName = DirNameFromString(m_actualName, parentDir.absolutePath()); + container.rename(parentDir.absoluteFilePath(dirName)); + + return true; } static QString read_string (nbt::value& parent, const char * name, const QString & fallback = QString()) @@ -184,34 +303,18 @@ static int64_t read_long (nbt::value& parent, const char * name, const int64_t & } }; -void World::parseLevelDat(QByteArray data) +void World::loadFromLevelDat(QByteArray data) { - QByteArray output; - is_valid = GZip::unzip(data, output); - if(!is_valid) - { - return; - } - try { - std::istringstream foo(std::string(output.constData(), output.size())); - auto pair = nbt::io::read_compound(foo); - is_valid = pair.first == ""; - - if(!is_valid) + auto levelData = parseLevelDat(data); + if(!levelData) { - return; - } - std::ostringstream ostr; - is_valid = pair.second != nullptr; - if(!is_valid) - { - qDebug() << "FAIL~!!!"; + is_valid = false; return; } - auto &val = pair.second->at("Data"); + auto &val = levelData->at("Data"); is_valid = val.get_type() == nbt::tag_type::Compound; if(!is_valid) return; diff --git a/logic/minecraft/World.h b/logic/minecraft/World.h index bed8f967..3cde5ea4 100644 --- a/logic/minecraft/World.h +++ b/logic/minecraft/World.h @@ -17,7 +17,9 @@ #include #include -class World +#include "multimc_logic_export.h" + +class MULTIMC_LOGIC_EXPORT World { public: World(const QFileInfo &file); @@ -56,7 +58,8 @@ public: // change the world's filesystem path (used by world lists for *MAGIC* purposes) void repath(const QFileInfo &file); - bool install(QString to); + bool rename(const QString &to); + bool install(const QString &to, const QString &name= QString()); // WEAK compare operator - used for replacing worlds bool operator==(const World &other) const; @@ -65,7 +68,7 @@ public: private: void readFromZip(const QFileInfo &file); void readFromFS(const QFileInfo &file); - void parseLevelDat(QByteArray data); + void loadFromLevelDat(QByteArray data); protected: diff --git a/logic/minecraft/WorldList.cpp b/logic/minecraft/WorldList.cpp index e58ca064..fff4f74a 100644 --- a/logic/minecraft/WorldList.cpp +++ b/logic/minecraft/WorldList.cpp @@ -164,6 +164,10 @@ QVariant WorldList::data(const QModelIndex &index, int role) const { return world.folderName(); } + case ObjectRole: + { + return QVariant::fromValue((void *)&world); + } case FolderRole: { return QDir::toNativeSeparators(dir().absoluteFilePath(world.folderName())); @@ -335,7 +339,11 @@ bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int r QString filename = url.toLocalFile(); QFileInfo worldInfo(filename); - installWorld(worldInfo); + + if(!m_dir.entryInfoList().contains(worldInfo)) + { + installWorld(worldInfo); + } } if (was_watching) startWatching(); diff --git a/logic/minecraft/WorldList.h b/logic/minecraft/WorldList.h index 90c7c6ed..34b30e9c 100644 --- a/logic/minecraft/WorldList.h +++ b/logic/minecraft/WorldList.h @@ -38,7 +38,8 @@ public: enum Roles { - FolderRole = Qt::UserRole + 1, + ObjectRole = Qt::UserRole + 1, + FolderRole, SeedRole, NameRole, LastPlayedRole