It is mainly a recursive algorithm that serializes data into a single file, each with its relative path to the parent folder. And deserializing is just taking binary data, creating needed subfolders, and saving the file.
Download: QtFolderCompressor.zip
[FolderCompressor.h]
#ifndef FOLDERCOMPRESSOR_H
#define FOLDERCOMPRESSOR_H
#include <QFile>
#include <QObject>
#include <QDir>
class FolderCompressor : public QObject
{
Q_OBJECT
public:
explicit FolderCompressor(QObject *parent = 0);
//A recursive function that scans all files inside the source folder
//and serializes all files in a row of file names and compressed
//binary data in a single file
bool compressFolder(QString sourceFolder, QString destinationFile);
//A function that deserializes data from the compressed file and
//creates any needed subfolders before saving the file
bool decompressFolder(QString sourceFile, QString destinationFolder);
private:
QFile file;
QDataStream dataStream;
bool compress(QString sourceFolder, QString prefex);
};
#endif // FOLDERCOMPRESSOR_H
[FolderCompressor.cpp]
#include "FolderCompressor.h"
FolderCompressor::FolderCompressor(QObject *parent) :
QObject(parent)
{
}
bool FolderCompressor::compressFolder(QString sourceFolder, QString destinationFile)
{
QDir src(sourceFolder);
if(!src.exists())//folder not found
{
return false;
}
file.setFileName(destinationFile);
if(!file.open(QIODevice::WriteOnly))//could not open file
{
return false;
}
dataStream.setDevice(&file);
bool success = compress(sourceFolder, "");
file.close();
return success;
}
bool FolderCompressor::compress(QString sourceFolder, QString prefex)
{
QDir dir(sourceFolder);
if(!dir.exists())
return false;
//1 - list all folders inside the current folder
dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs);
QFileInfoList foldersList = dir.entryInfoList();
//2 - For each folder in list: call the same function with folders' paths
for(int i=0; i<foldersList.length(); i++)
{
QString folderName = foldersList.at(i).fileName();
QString folderPath = dir.absolutePath()+"/"+folderName;
QString newPrefex = prefex+"/"+folderName;
compress(folderPath, newPrefex);
}
//3 - List all files inside the current folder
dir.setFilter(QDir::NoDotAndDotDot | QDir::Files);
QFileInfoList filesList = dir.entryInfoList();
//4- For each file in list: add file path and compressed binary data
for(int i=0; i<filesList.length(); i++)
{
QFile file(dir.absolutePath()+"/"+filesList.at(i).fileName());
if(!file.open(QIODevice::ReadOnly))//couldn't open file
{
return false;
}
dataStream << QString(prefex+"/"+filesList.at(i).fileName());
dataStream << qCompress(file.readAll());
file.close();
}
return true;
}
bool FolderCompressor::decompressFolder(QString sourceFile, QString destinationFolder)
{
//validation
QFile src(sourceFile);
if(!src.exists())
{//file not found, to handle later
return false;
}
QDir dir;
if(!dir.mkpath(destinationFolder))
{//could not create folder
return false;
}
file.setFileName(sourceFile);
if(!file.open(QIODevice::ReadOnly))
return false;
dataStream.setDevice(&file);
while(!dataStream.atEnd())
{
QString fileName;
QByteArray data;
//extract file name and data in order
dataStream >> fileName >> data;
//create any needed folder
QString subfolder;
for(int i=fileName.length()-1; i>0; i--)
{
if((QString(fileName.at(i)) == QString("\\")) || (QString(fileName.at(i)) == QString("/")))
{
subfolder = fileName.left(i);
dir.mkpath(destinationFolder+"/"+subfolder);
break;
}
}
QFile outFile(destinationFolder+"/"+fileName);
if(!outFile.open(QIODevice::WriteOnly))
{
file.close();
return false;
}
outFile.write(qUncompress(data));
outFile.close();
}
file.close();
return true;
}
Nice :-) Thanks a lot!!
ReplyDeleteWelcome.
DeleteTwo features that you missed. Hidden folders and files prefixed with "." and empty folders.
ReplyDeleteTo include hidden files change filters to:
dir.setFilter(QDir::NoDotAndDotDot | QDir::Hidden | QDir::Dirs);
dir.setFilter(QDir::NoDotAndDotDot | QDir::Hidden | QDir::Files);
To add empty folders:
In compress():
if(filesList.length() == 0) {
qDebug() << "Compressing folder: " << prefex << "/";
dataStream << QString(prefex + "/");
dataStream << " ";
}
In decompress():
if(fileName.endsWith("/") == false) {
...decompress code here...
} else {
dir.mkpath(destinationFolder + "/" + fileName);
}
Very nice, thank you so much!
ReplyDeleteDo you know an easy way to compress to ZIp format with this class?
ReplyDeleteI remember that you need to use a specific lib for zip format.
DeleteThanks for your answer. I expected that you had another answer :)
ReplyDeleteWith this class I can't export my compressed files to an external application, only to applications that have this class to uncompress! It is a pity...
By the way, do you have any suggestion to ZIP code in C++ classes/library to easily integrate with Qt?
Thank you very much!!!
ReplyDeleteThk u so !
ReplyDeleteI tried with above code, it works great, thanks to you for that ,
ReplyDeletebut if any file in the source directory is open in another application then compression fails
This comment has been removed by a blog administrator.
ReplyDeleteHi do you have limitations on folder size that you can compress? I have tried folder that have only one file, whose size is 4GB but destination file is only 1 KB in size.
ReplyDeleteI don't have and specific limitations. But for a file this big, it could be a memory issue because I simply read the file and add it. So, reading 4GB can be problematic :)
DeleteMaybe you can try changing the code to stream the data. Maybe it works