Thursday, June 2, 2011

Qt Folder Compression

I needed a compression module that does not depend on anything but Qt, and I mean a module to compress a complete folder with its children. This is because I wanted the compression module to work on any platform supported by Qt with no other dependencies. So I made this modules that only uses only qCompress() & qUncompress() methods plus some logic.

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;
}

11 comments:

  1. Nice :-) Thanks a lot!!

    ReplyDelete
  2. Two features that you missed. Hidden folders and files prefixed with "." and empty folders.

    To 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);
    }

    ReplyDelete
  3. Very nice, thank you so much!

    ReplyDelete
  4. Do you know an easy way to compress to ZIp format with this class?

    ReplyDelete
    Replies
    1. I remember that you need to use a specific lib for zip format.

      Delete
  5. Thanks for your answer. I expected that you had another answer :)

    With 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?

    ReplyDelete
  6. Thank you very much!!!

    ReplyDelete
  7. I tried with above code, it works great, thanks to you for that ,

    but if any file in the source directory is open in another application then compression fails

    ReplyDelete
  8. wihh nice info, saya pengunjung setia web anda
    kunjung balik, di web kami banyak penawaran dan tips tentang kesehatan
    Ada artikel menarik tentang obat tradisional yang mampu menyembuhkan penyakit berat, cek yuk
    Obat tradisional Uveitis

    ReplyDelete