GH-1227 allow structured world zip import and drag and drop out of MultiMC
This commit is contained in:
		
							
								
								
									
										151
									
								
								logic/MMCZip.cpp
									
									
									
									
									
								
							
							
						
						
									
										151
									
								
								logic/MMCZip.cpp
									
									
									
									
									
								
							| @@ -26,6 +26,7 @@ see quazip/(un)MMCZip.h files for details. Basically it's the zlib license. | ||||
| #include <pathutils.h> | ||||
| #include <quazip.h> | ||||
| #include <JlCompress.h> | ||||
| #include <quazipdir.h> | ||||
| #include "MMCZip.h" | ||||
|  | ||||
| #include <QDebug> | ||||
| @@ -338,3 +339,153 @@ bool MMCZip::compressDir(QString zipFile, QString dir, QString prefix, const Sep | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| QString MMCZip::findFileInZip(QuaZip * zip, const QString & what, const QString &root) | ||||
| { | ||||
| 	QuaZipDir rootDir(zip, root); | ||||
| 	for(auto fileName: rootDir.entryList(QDir::Files)) | ||||
| 	{ | ||||
| 		if(fileName == what) | ||||
| 			return root; | ||||
| 	} | ||||
| 	for(auto fileName: rootDir.entryList(QDir::Dirs)) | ||||
| 	{ | ||||
| 		QString result = findFileInZip(zip, what, root + fileName); | ||||
| 		if(!result.isEmpty()) | ||||
| 		{ | ||||
| 			return result; | ||||
| 		} | ||||
| 	} | ||||
| 	return QString(); | ||||
| } | ||||
|  | ||||
| bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root) | ||||
| { | ||||
| 	QuaZipDir rootDir(zip, root); | ||||
| 	for(auto fileName: rootDir.entryList(QDir::Files)) | ||||
| 	{ | ||||
| 		if(fileName == what) | ||||
| 		{ | ||||
| 			result.append(root); | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	for(auto fileName: rootDir.entryList(QDir::Dirs)) | ||||
| 	{ | ||||
| 		findFilesInZip(zip, what, result, root + fileName); | ||||
| 	} | ||||
| 	return !result.isEmpty(); | ||||
| } | ||||
|  | ||||
| bool removeFile(QStringList listFile) | ||||
| { | ||||
| 	bool ret = true; | ||||
| 	for (int i = 0; i < listFile.count(); i++) | ||||
| 	{ | ||||
| 		ret &= QFile::remove(listFile.at(i)); | ||||
| 	} | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| bool MMCZip::extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest) | ||||
| { | ||||
| 	if(!zip) | ||||
| 		return false; | ||||
|  | ||||
| 	if (zip->getMode() != QuaZip::mdUnzip) | ||||
| 		return false; | ||||
|  | ||||
| 	if (!fileName.isEmpty()) | ||||
| 		zip->setCurrentFile(fileName); | ||||
|  | ||||
| 	QuaZipFile inFile(zip); | ||||
| 	if (!inFile.open(QIODevice::ReadOnly) || inFile.getZipError() != UNZ_OK) | ||||
| 		return false; | ||||
|  | ||||
| 	// Controllo esistenza cartella file risultato | ||||
| 	QDir curDir; | ||||
| 	if (fileDest.endsWith('/')) | ||||
| 	{ | ||||
| 		if (!curDir.mkpath(fileDest)) | ||||
| 		{ | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		if (!curDir.mkpath(QFileInfo(fileDest).absolutePath())) | ||||
| 		{ | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	QuaZipFileInfo64 info; | ||||
| 	if (!zip->getCurrentFileInfo(&info)) | ||||
| 		return false; | ||||
|  | ||||
| 	QFile::Permissions srcPerm = info.getPermissions(); | ||||
| 	if (fileDest.endsWith('/') && QFileInfo(fileDest).isDir()) | ||||
| 	{ | ||||
| 		if (srcPerm != 0) | ||||
| 		{ | ||||
| 			QFile(fileDest).setPermissions(srcPerm); | ||||
| 		} | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	QFile outFile; | ||||
| 	outFile.setFileName(fileDest); | ||||
| 	if (!outFile.open(QIODevice::WriteOnly)) | ||||
| 		return false; | ||||
|  | ||||
| 	if (!copyData(inFile, outFile) || inFile.getZipError() != UNZ_OK) | ||||
| 	{ | ||||
| 		outFile.close(); | ||||
| 		removeFile(QStringList(fileDest)); | ||||
| 		return false; | ||||
| 	} | ||||
| 	outFile.close(); | ||||
|  | ||||
| 	inFile.close(); | ||||
| 	if (inFile.getZipError() != UNZ_OK) | ||||
| 	{ | ||||
| 		removeFile(QStringList(fileDest)); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (srcPerm != 0) | ||||
| 	{ | ||||
| 		outFile.setPermissions(srcPerm); | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target) | ||||
| { | ||||
| 	QDir directory(target); | ||||
| 	QStringList extracted; | ||||
| 	if (!zip->goToFirstFile()) | ||||
| 	{ | ||||
| 		return QStringList(); | ||||
| 	} | ||||
| 	do | ||||
| 	{ | ||||
| 		QString name = zip->getCurrentFileName(); | ||||
| 		if(!name.startsWith(subdir)) | ||||
| 		{ | ||||
| 			continue; | ||||
| 		} | ||||
| 		name.remove(0, subdir.size()); | ||||
| 		QString absFilePath = directory.absoluteFilePath(name); | ||||
| 		if(name.isEmpty()) | ||||
| 		{ | ||||
| 			absFilePath += "/"; | ||||
| 		} | ||||
| 		if (!extractFile(zip, "", absFilePath)) | ||||
| 		{ | ||||
| 			removeFile(extracted); | ||||
| 			return QStringList(); | ||||
| 		} | ||||
| 		extracted.append(absFilePath); | ||||
| 	} while (zip->goToNextFile()); | ||||
| 	return extracted; | ||||
| } | ||||
|   | ||||
| @@ -58,4 +58,31 @@ namespace MMCZip | ||||
| 	 * \return The list of the full paths of the files extracted, empty on failure. | ||||
| 	 */ | ||||
| 	QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir = QString()); | ||||
|  | ||||
| 	/** | ||||
| 	 * Find a single file in archive by file name (not path) | ||||
| 	 * | ||||
| 	 * \return the path prefix where the file is | ||||
| 	 */ | ||||
| 	QString MULTIMC_LOGIC_EXPORT findFileInZip(QuaZip * zip, const QString & what, const QString &root = QString()); | ||||
|  | ||||
| 	/** | ||||
| 	 * Find a multiple files of the same name in archive by file name | ||||
| 	 * If a file is found in a path, no deeper paths are searched | ||||
| 	 * | ||||
| 	 * \return true if anything was found | ||||
| 	 */ | ||||
| 	bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString()); | ||||
|  | ||||
| 	/** | ||||
| 	 * Extract a single file to a destination | ||||
| 	 * | ||||
| 	 * \return true if it succeeds | ||||
| 	 */ | ||||
| 	bool MULTIMC_LOGIC_EXPORT extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest); | ||||
|  | ||||
| 	/** | ||||
| 	 * Extract a subdirectory from an archive | ||||
| 	 */ | ||||
| 	QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target); | ||||
| } | ||||
| @@ -27,6 +27,7 @@ | ||||
| #include <tag_primitive.h> | ||||
| #include <quazip.h> | ||||
| #include <quazipfile.h> | ||||
| #include <quazipdir.h> | ||||
|  | ||||
| World::World(const QFileInfo &file) | ||||
| { | ||||
| @@ -76,9 +77,16 @@ void World::readFromZip(const QFileInfo &file) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	auto location = MMCZip::findFileInZip(&zip, "level.dat"); | ||||
| 	is_valid = !location.isEmpty(); | ||||
| 	if (!is_valid) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	m_containerOffsetPath = location; | ||||
| 	QuaZipFile zippedFile(&zip); | ||||
| 	// read the install profile | ||||
| 	is_valid = zip.setCurrentFile("level.dat"); | ||||
| 	is_valid = zip.setCurrentFile(location + "level.dat"); | ||||
| 	if (!is_valid) | ||||
| 	{ | ||||
| 		return; | ||||
| @@ -109,8 +117,12 @@ bool World::install(QString to) | ||||
| 	} | ||||
| 	if(m_containerFile.isFile()) | ||||
| 	{ | ||||
| 		// FIXME: check if this is OK. | ||||
| 		return !MMCZip::extractDir(m_containerFile.absoluteFilePath(), finalPath).isEmpty(); | ||||
| 		QuaZip zip(m_containerFile.absoluteFilePath()); | ||||
| 		if (!zip.open(QuaZip::mdUnzip)) | ||||
| 		{ | ||||
| 			return false; | ||||
| 		} | ||||
| 		return !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath).isEmpty(); | ||||
| 	} | ||||
| 	else if(m_containerFile.isDir()) | ||||
| 	{ | ||||
|   | ||||
| @@ -41,6 +41,14 @@ public: | ||||
| 	{ | ||||
| 		return is_valid; | ||||
| 	} | ||||
| 	bool isOnFS() const | ||||
| 	{ | ||||
| 		return m_containerFile.isDir(); | ||||
| 	} | ||||
| 	QFileInfo container() const | ||||
| 	{ | ||||
| 		return m_containerFile; | ||||
| 	} | ||||
| 	// delete all the files of this world | ||||
| 	bool destroy(); | ||||
| 	// replace this world with a copy of the other | ||||
| @@ -62,6 +70,7 @@ private: | ||||
| protected: | ||||
|  | ||||
| 	QFileInfo m_containerFile; | ||||
| 	QString m_containerOffsetPath; | ||||
| 	QString m_folderName; | ||||
| 	QString m_actualName; | ||||
| 	QDateTime levelDatTime; | ||||
|   | ||||
| @@ -219,25 +219,64 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol | ||||
| QStringList WorldList::mimeTypes() const | ||||
| { | ||||
| 	QStringList types; | ||||
| 	types << "text/plain"; | ||||
| 	types << "text/uri-list"; | ||||
| 	return types; | ||||
| } | ||||
|  | ||||
| class WorldMimeData : public QMimeData | ||||
| { | ||||
| Q_OBJECT | ||||
|  | ||||
| public: | ||||
| 	WorldMimeData(QList<World> worlds) | ||||
| 	{ | ||||
| 		m_worlds = worlds; | ||||
|  | ||||
| 	} | ||||
| 	QStringList formats() const | ||||
| 	{ | ||||
| 		return QMimeData::formats() << "text/uri-list"; | ||||
| 	} | ||||
|  | ||||
| protected: | ||||
| 	QVariant retrieveData(const QString &mimetype, QVariant::Type type) const | ||||
| 	{ | ||||
| 		QList<QUrl> urls; | ||||
| 		for(auto &world: m_worlds) | ||||
| 		{ | ||||
| 			if(!world.isValid() || !world.isOnFS()) | ||||
| 				continue; | ||||
| 			QString worldPath = world.container().absoluteFilePath(); | ||||
| 			qDebug() << worldPath; | ||||
| 			urls.append(QUrl::fromLocalFile(worldPath)); | ||||
| 		} | ||||
| 		const_cast<WorldMimeData*>(this)->setUrls(urls); | ||||
| 		return QMimeData::retrieveData(mimetype, type); | ||||
| 	} | ||||
| private: | ||||
| 	QList<World> m_worlds; | ||||
| }; | ||||
|  | ||||
| QMimeData *WorldList::mimeData(const QModelIndexList &indexes) const | ||||
| { | ||||
| 	QMimeData *data = new QMimeData(); | ||||
|  | ||||
| 	if (indexes.size() == 0) | ||||
| 		return data; | ||||
| 		return new QMimeData(); | ||||
|  | ||||
| 	auto idx = indexes[0]; | ||||
| 	QList<World> worlds; | ||||
| 	for(auto idx : indexes) | ||||
| 	{ | ||||
| 		if(idx.column() != 0) | ||||
| 			continue; | ||||
| 		int row = idx.row(); | ||||
| 	if (row < 0 || row >= worlds.size()) | ||||
| 		return data; | ||||
|  | ||||
| 	data->setText(QString::number(row)); | ||||
| 	return data; | ||||
| 		if (row < 0 || row >= this->worlds.size()) | ||||
| 			continue; | ||||
| 		worlds.append(this->worlds[row]); | ||||
| 	} | ||||
| 	if(!worlds.size()) | ||||
| 	{ | ||||
| 		return new QMimeData(); | ||||
| 	} | ||||
| 	return new WorldMimeData(worlds); | ||||
| } | ||||
|  | ||||
| Qt::ItemFlags WorldList::flags(const QModelIndex &index) const | ||||
| @@ -302,27 +341,7 @@ bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int r | ||||
| 			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; | ||||
|  | ||||
| } | ||||
| #include "WorldList.moc" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user