From a1fd50e920eba0f198b898e5df4ff5f60424d355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Wed, 9 Sep 2015 23:53:33 +0200 Subject: GH-1227: World import using drag and drop - zip files and folders --- logic/minecraft/ModList.cpp | 1 + logic/minecraft/World.cpp | 188 ++++++++++++++++++++++++++++-------------- logic/minecraft/World.h | 10 ++- logic/minecraft/WorldList.cpp | 116 +++++++++++++++++++++----- logic/minecraft/WorldList.h | 15 +++- 5 files changed, 247 insertions(+), 83 deletions(-) (limited to 'logic') diff --git a/logic/minecraft/ModList.cpp b/logic/minecraft/ModList.cpp index a77c5890..8f1bc041 100644 --- a/logic/minecraft/ModList.cpp +++ b/logic/minecraft/ModList.cpp @@ -539,6 +539,7 @@ QMimeData *ModList::mimeData(const QModelIndexList &indexes) const data->setText(params.join('|')); return data; } + bool ModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { diff --git a/logic/minecraft/World.cpp b/logic/minecraft/World.cpp index 977a03cf..3b66f113 100644 --- a/logic/minecraft/World.cpp +++ b/logic/minecraft/World.cpp @@ -20,10 +20,13 @@ #include #include "GZip.h" +#include #include #include #include #include +#include +#include World::World(const QFileInfo &file) { @@ -32,8 +35,20 @@ World::World(const QFileInfo &file) void World::repath(const QFileInfo &file) { - m_file = file; + m_containerFile = file; m_folderName = file.fileName(); + if(file.isFile() && file.suffix() == "zip") + { + readFromZip(file); + } + else if(file.isDir()) + { + readFromFS(file); + } +} + +void World::readFromFS(const QFileInfo &file) +{ QDir worldDir(file.filePath()); is_valid = file.isDir() && worldDir.exists("level.dat"); if(!is_valid) @@ -48,66 +63,123 @@ void World::repath(const QFileInfo &file) { return; } + QFileInfo finfo(fullFilePath); + levelDatTime = finfo.lastModified(); + parseLevelDat(f.readAll()); +} - QByteArray output; - is_valid = GZip::inflate(f.readAll(), output); - if(!is_valid) +void World::readFromZip(const QFileInfo &file) +{ + QuaZip zip(file.absoluteFilePath()); + is_valid = zip.open(QuaZip::mdUnzip); + if (!is_valid) + { + return; + } + QuaZipFile zippedFile(&zip); + // read the install profile + is_valid = zip.setCurrentFile("level.dat"); + if (!is_valid) { return; } - f.close(); + is_valid = zippedFile.open(QIODevice::ReadOnly); + QuaZipFileInfo64 levelDatInfo; + zippedFile.getFileInfo(&levelDatInfo); + auto modTime = levelDatInfo.getNTFSmTime(); + if(!modTime.isValid()) + { + modTime = levelDatInfo.dateTime; + } + levelDatTime = modTime; + if (!is_valid) + { + return; + } + parseLevelDat(zippedFile.readAll()); + zippedFile.close(); +} - auto read_string = [](nbt::value& parent, const char * name, const QString & fallback = QString()) -> QString +bool World::install(QString to) +{ + auto finalPath = PathCombine(to, DirNameFromString(m_actualName, to)); + if(!ensureFolderPathExists(finalPath)) { - try - { - auto &namedValue = parent.at(name); - if(namedValue.get_type() != nbt::tag_type::String) - { - return fallback; - } - auto & tag_str = namedValue.as(); - return QString::fromStdString(tag_str.get()); - } - catch(std::out_of_range e) - { - // fallback for old world formats - qWarning() << "String NBT tag" << name << "could not be found. Defaulting to" << fallback; - return fallback; - } - catch(std::bad_cast e) - { - // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to string. Defaulting to" << fallback; - return fallback; - } - }; + return false; + } + if(m_containerFile.isFile()) + { + // FIXME: check if this is OK. + return !MMCZip::extractDir(m_containerFile.absoluteFilePath(), finalPath).isEmpty(); + } + else if(m_containerFile.isDir()) + { + QString from = m_containerFile.filePath(); + return copyPath(from, finalPath); + } + return false; +} - auto read_long = [](nbt::value& parent, const char * name, const int64_t & fallback = 0) -> int64_t +static QString read_string (nbt::value& parent, const char * name, const QString & fallback = QString()) +{ + try { - try - { - auto &namedValue = parent.at(name); - if(namedValue.get_type() != nbt::tag_type::Long) - { - return fallback; - } - auto & tag_str = namedValue.as(); - return tag_str.get(); - } - catch(std::out_of_range e) + auto &namedValue = parent.at(name); + if(namedValue.get_type() != nbt::tag_type::String) { - // fallback for old world formats - qWarning() << "Long NBT tag" << name << "could not be found. Defaulting to" << fallback; return fallback; } - catch(std::bad_cast e) + auto & tag_str = namedValue.as(); + return QString::fromStdString(tag_str.get()); + } + catch(std::out_of_range e) + { + // fallback for old world formats + qWarning() << "String NBT tag" << name << "could not be found. Defaulting to" << fallback; + return fallback; + } + catch(std::bad_cast e) + { + // type mismatch + qWarning() << "NBT tag" << name << "could not be converted to string. Defaulting to" << fallback; + return fallback; + } +}; + +static int64_t read_long (nbt::value& parent, const char * name, const int64_t & fallback = 0) +{ + try + { + auto &namedValue = parent.at(name); + if(namedValue.get_type() != nbt::tag_type::Long) { - // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to long. Defaulting to" << fallback; return fallback; } - }; + auto & tag_str = namedValue.as(); + return tag_str.get(); + } + catch(std::out_of_range e) + { + // fallback for old world formats + qWarning() << "Long NBT tag" << name << "could not be found. Defaulting to" << fallback; + return fallback; + } + catch(std::bad_cast e) + { + // type mismatch + qWarning() << "NBT tag" << name << "could not be converted to long. Defaulting to" << fallback; + return fallback; + } +}; + +void World::parseLevelDat(QByteArray data) +{ + QByteArray output; + is_valid = GZip::inflate(data, output); + if(!is_valid) + { + return; + } try { @@ -138,8 +210,7 @@ void World::repath(const QFileInfo &file) int64_t temp = read_long(val, "LastPlayed", 0); if(temp == 0) { - QFileInfo finfo(fullFilePath); - m_lastPlayed = finfo.lastModified(); + m_lastPlayed = levelDatTime; } else { @@ -164,11 +235,11 @@ bool World::replace(World &with) { if (!destroy()) return false; - bool success = copyPath(with.m_file.filePath(), m_file.path()); + bool success = copyPath(with.m_containerFile.filePath(), m_containerFile.path()); if (success) { m_folderName = with.m_folderName; - m_file.refresh(); + m_containerFile.refresh(); } return success; } @@ -176,18 +247,15 @@ bool World::replace(World &with) bool World::destroy() { if(!is_valid) return false; - if (m_file.isDir()) + if (m_containerFile.isDir()) { - QDir d(m_file.filePath()); - if (d.removeRecursively()) - { - return true; - } - return false; + QDir d(m_containerFile.filePath()); + return d.removeRecursively(); } - else + else if(m_containerFile.isFile()) { - return false; + QFile file(m_containerFile.absoluteFilePath()); + return file.remove(); } return true; } diff --git a/logic/minecraft/World.h b/logic/minecraft/World.h index 91cb2a83..27184e05 100644 --- a/logic/minecraft/World.h +++ b/logic/minecraft/World.h @@ -48,15 +48,23 @@ public: // change the world's filesystem path (used by world lists for *MAGIC* purposes) void repath(const QFileInfo &file); + bool install(QString to); + // WEAK compare operator - used for replacing worlds bool operator==(const World &other) const; bool strongCompare(const World &other) const; +private: + void readFromZip(const QFileInfo &file); + void readFromFS(const QFileInfo &file); + void parseLevelDat(QByteArray data); + protected: - QFileInfo m_file; + QFileInfo m_containerFile; QString m_folderName; QString m_actualName; + QDateTime levelDatTime; QDateTime m_lastPlayed; int64_t m_randomSeed = 0; bool is_valid = false; diff --git a/logic/minecraft/WorldList.cpp b/logic/minecraft/WorldList.cpp index 7066093c..1c25f3b1 100644 --- a/logic/minecraft/WorldList.cpp +++ b/logic/minecraft/WorldList.cpp @@ -62,40 +62,28 @@ void WorldList::stopWatching() } } -void WorldList::internalSort(QList &what) -{ - auto predicate = [](const World &left, const World &right) - { - return left.folderName().localeAwareCompare(right.folderName()) < 0; - }; - std::sort(what.begin(), what.end(), predicate); -} - bool WorldList::update() { if (!isValid()) return false; - QList orderedWorlds; QList newWorlds; m_dir.refresh(); auto folderContents = m_dir.entryInfoList(); // if there are any untracked files... - if (folderContents.size()) + for (QFileInfo entry : folderContents) { - // the order surely changed! - for (auto entry : folderContents) + if(!entry.isDir()) + continue; + + World w(entry); + if(w.isValid()) { - World w(entry); - if(w.isValid()) { - newWorlds.append(w); - } + newWorlds.append(w); } - internalSort(newWorlds); - orderedWorlds.append(newWorlds); } beginResetModel(); - worlds.swap(orderedWorlds); + worlds.swap(newWorlds); endResetModel(); return true; } @@ -232,6 +220,7 @@ QStringList WorldList::mimeTypes() const { QStringList types; types << "text/plain"; + types << "text/uri-list"; return types; } @@ -250,3 +239,90 @@ QMimeData *WorldList::mimeData(const QModelIndexList &indexes) const data->setText(QString::number(row)); return data; } + +Qt::ItemFlags WorldList::flags(const QModelIndex &index) const +{ + Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); + if (index.isValid()) + return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | + defaultFlags; + else + return Qt::ItemIsDropEnabled | defaultFlags; +} + +Qt::DropActions WorldList::supportedDragActions() const +{ + // move to other mod lists or VOID + return Qt::MoveAction; +} + +Qt::DropActions WorldList::supportedDropActions() const +{ + // copy from outside, move from within and other mod lists + return Qt::CopyAction | Qt::MoveAction; +} + +void WorldList::installWorld(QFileInfo filename) +{ + qDebug() << "installing: " << filename.absoluteFilePath(); + World w(filename); + if(!w.isValid()) + { + return; + } + w.install(m_dir.absolutePath()); +} + +bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, + const QModelIndex &parent) +{ + if (action == Qt::IgnoreAction) + return true; + // check if the action is supported + if (!data || !(action & supportedDropActions())) + return false; + // files dropped from outside? + if (data->hasUrls()) + { + bool was_watching = is_watching; + if (was_watching) + stopWatching(); + auto urls = data->urls(); + for (auto url : urls) + { + // only local files may be dropped... + if (!url.isLocalFile()) + continue; + QString filename = url.toLocalFile(); + + QFileInfo worldInfo(filename); + installWorld(worldInfo); + } + if (was_watching) + startWatching(); + return true; + } + /* + else if (data->hasText()) + { + QString sourcestr = data->text(); + auto list = sourcestr.split('|'); + if (list.size() != 2) + return false; + QString remoteId = list[0]; + int remoteIndex = list[1].toInt(); + qDebug() << "move: " << sourcestr; + // no moving of things between two lists + if (remoteId != m_list_id) + return false; + // no point moving to the same place... + if (row == remoteIndex) + return false; + // otherwise, move the mod :D + moveModTo(remoteIndex, row); + return true; + } + */ + return false; + +} diff --git a/logic/minecraft/WorldList.h b/logic/minecraft/WorldList.h index 7f119e81..90c7c6ed 100644 --- a/logic/minecraft/WorldList.h +++ b/logic/minecraft/WorldList.h @@ -19,6 +19,7 @@ #include #include #include +#include #include "minecraft/World.h" #include "multimc_logic_export.h" @@ -71,16 +72,28 @@ public: /// Reloads the mod list and returns true if the list changed. virtual bool update(); + /// Install a world from location + void installWorld(QFileInfo filename); + /// Deletes the mod at the given index. virtual bool deleteWorld(int index); /// Deletes all the selected mods virtual bool deleteWorlds(int first, int last); + /// flags, mostly to support drag&drop + virtual Qt::ItemFlags flags(const QModelIndex &index) const; /// get data for drag action virtual QMimeData *mimeData(const QModelIndexList &indexes) const; /// get the supported mime types virtual QStringList mimeTypes() const; + /// process data from drop action + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + /// what drag actions do we support? + virtual Qt::DropActions supportedDragActions() const; + + /// what drop actions do we support? + virtual Qt::DropActions supportedDropActions() const; void startWatching(); void stopWatching(); @@ -97,8 +110,6 @@ public: return worlds; } -private: - void internalSort(QList &what); private slots: void directoryChanged(QString path); -- cgit v1.2.3