How can I zip a directory/folder with quazip? - c++

I have a directory with files and folders that I would like to zip. I'm using the qt-project quazip for it. So I thought I write a function that packs all content of a directory including the filestructure.
How can I create the folder in the zip-file? I tried it with QuaZipNewInfo but couldn't make it work.
For example I want to zip the tmp-folder with this content:
tmp/1.txt
tmp/folder1/2.txt
tmp/folder1/3.txt
tmp/folder2/4.txt
tmp/folder2/folder3/5.txt
What I get after extracting the file with a common archive-tool (Archive Utility) is this:
tmp/1.txt
tmp/2.txt
tmp/3.txt
tmp/4.txt
tmp/5.txt
This is what I have so far:
void Exporter::zipFilelist(QFileInfoList& files, QuaZipFile& outFile, QFile& inFile, QFile& inFileTmp)
{
char c;
foreach(QFileInfo file, files) {
if(file.isDir() && file.fileName() != "." && file.fileName() != "..") {
QFileInfoList infoList = QDir(file.filePath()).entryInfoList();
zipFilelist(infoList, outFile, inFile, inFileTmp);
}
if(file.isFile()) {
inFileTmp.setFileName(file.fileName());
inFile.setFileName(file.filePath());
if(!inFile.open(QIODevice::ReadOnly)) {
qDebug() << "testCreate(): inFile.open(): " << inFile.errorString().toLocal8Bit().constData();
}
QuaZipNewInfo info(inFileTmp.fileName(), inFile.fileName());
if(!outFile.open(QIODevice::WriteOnly, info)) {
qDebug() << "testCreate(): outFile.open(): " << outFile.getZipError();
}
while(inFile.getChar(&c)&&outFile.putChar(c)) ;
if(outFile.getZipError()!=UNZ_OK) {
qDebug() << "testCreate(): outFile.putChar(): %d"<< outFile.getZipError();
}
outFile.close();
if(outFile.getZipError()!=UNZ_OK) {
qDebug() << "testCreate(): outFile.close(): %d"<< outFile.getZipError();
}
inFile.close();
}
}
}
And this is how I call the function:
QFileInfoList files = QDir(sourceFolder).entryInfoList();
QFile inFile;
QFile inFileTmp;
QuaZipFile outFile(&zip);
zipFilelist(files, outFile, inFile, inFileTmp);

I don't get any error. When I want to unzip the file it doesn't extract the folders (because I probably don't pack them into the zip!?). So I get all files of all subfolders unziped into one folder.
It seems that in your function you were recursively getting the files in the folders, but not the folders themselves. Try creating a folder to zip the files into when you recurse into looking for the files in the subdirectory.
You may want to look into this answer:
https://stackoverflow.com/a/2598649/1819900
How about the utilities provided by QuaZip?
http://quazip.sourceforge.net/classJlCompress.html

When creating the QuaZipNewInfo object, specify the path and file name to your file as you want to store it in the zip as the first argument, and the path and file name to your file on disk as the second argument. Example:
Adding C:/test/myFile.txt as test/myFile.txt in zip:
QuaZipNewInfo("test/myFile.txt", "C:/test/myFile.txt")

In order to create a folder in your zip file, you need to create an empty file with a name ending with "/". The answer does not include the listing of files/folders but focuses on creating folders in zip file.
QDir sourceRootDir("/path/to/source/folder");
QStringList sourceFilesList; // list of path relative to source root folder
sourceFilesList << "relativePath.txt" << "folder" << "folder/relativePath";
QualZip zip("path/to/zip.zip");
if(!zip.open(QuaZip::mdCreate)){
return false;
}
QuaZipFile outZipFile(&zip);
// Copy file and folder to zip file
foreach (const QString &sourceFilePath, sourceFilesList) {
QFileInfo sourceFI(sourceRootDir.absoluteFilePath(sourceFilePath));
// FOLDER (this is the part that interests you!!!)
if(sourceFI.isFolder()){
QString sourceFolderPath = sourceFilePath;
if(!sourceFolderPath.endsWith("/")){
sourceFolderPath.append("/");
}
if(!outZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(sourceFolderPath, sourceFI.absoluteFilePath()))){
return false;
}
outZipFile.close();
// FILE
} else if(sourceFI.isFile()){
QFile inFile(sourceFI.absoluteFilePath());
if(!inFile.open(QIODevice::ReadOnly)){
zip.close();
return false;
}
// Note: since relative, source=dst
if(!outZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(sourceFilePath, sourceFI.absoluteFilePath()))){
inFile.close();
zip.close();
return false;
}
// Copy
qDebug() << " copy start";
QByteArray buffer;
int chunksize = 256; // Whatever chunk size you like
buffer = inFile.read(chunksize);
while(!buffer.isEmpty()){
qDebug() << " copy " << buffer.count();
outZipFile.write(buffer);
buffer = inFile.read(chunksize);
}
outZipFile.close();
inFile.close();
} else {
// Probably simlink, ignore
}
}
zip.close();
return true;

Related

Add new Directory with Poco::zip always give Exception

I'm building a Compress/Decompress functions and currently, I only able to compress 1 single file into a zip file. Each time I add a new Directory or add Recursively a Directory it always give me Exception.
Here is my ZipFile function:
Poco::DateTime(Timestamp);
set <string> extensionsSet;
std::ofstream fos(target, ios::binary);
Poco::Zip::Compress c(fos, true);
for (int i = 0; i < extensions.Size(); i++) {
string ext = std::dynamic_pointer_cast<String>(extensions.operator[](i))->GetPrimitive();
extensionsSet.insert(ext);
}
c.setStoreExtensions(extensionsSet);//set extensions List
Poco::File aFile(source);
if (aFile.exists())
{
Poco::Path p(aFile.path());
if (aFile.isDirectory())
{
Poco::Path sourceDir(source);
Poco::Path targetDir(target);
c.addDirectory(targetDir, Poco::DateTime(Timestamp));// give exception
targetDir.makeDirectory();
c.addRecursive(sourceDir);
}
else if (aFile.isFile())
{
c.addFile(p, p.getFileName());
}
}
else {
_log.EndMethod();
throw new FileNotFoundException("File Not Found");
}
c.close(); // MUST be done to finalize the Zip file
fos.close();
Here is where my exception, the underneath block of code is within addDirectory methods of Poco:
if (!ZipCommon::isValidPath(fileStr))
throw ZipException("Illegal entry name " + fileStr + " containing parent directory reference");
There is a member in "Poco::Path" called "absolute".
You can set absolute to false by constructor
otherwise the ZipException is thrown because this function returns "false":
bool ZipCommon::isValidPath(const std::string& path)
{
try
{
if (Path(path, Path::PATH_UNIX).isAbsolute() || Path(path, Path::PATH_WINDOWS).isAbsolute())
return false;
}
...
}
Here is a example:
std::ofstream fos(target, ios::binary);
Poco::Zip::Compress compress(fos, true);
std::vector<std::string> dirs; // parent dirs in zip file
Poco::Path myPath(false); // set path to non-absolute
// add parent directories
for (auto& dir : dirs)
{
myPath.pushDirectory(dir);
}
//add file
myPath.setFileName("FileName.txt");
//add file to archive
compress.addFile(theFile, relativePath);
//close archive and get zipArchiveObject
ZipArchive zipArchive(compress.close());

How to add non compilable configuration file into QT Project?

I have a .txt file in QT project, where I store settings parameters for my app. If I read it that way
void MainWindow::ReadApplicationSettings()
{
QFile cfgFile("config.txt");
if(!cfgFile.exists())
{
qDebug() << "Cannot find config file.";
}
else
{
ParseConfigFile(cfgFile);
SetCfg();
}
cfgFile.close();
}
And Parse it:
void MainWindow::ParseConfigFile(QFile &cfgFile)
{
QString line;
if (cfgFile.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream stream(&cfgFile);
while (!stream.atEnd())
{
line = stream.readLine();
std::istringstream iss(line.toStdString());
std::string id, eq, val;
bool error = false;
if (!(iss >> id))
{
error = true;
}
else if (id[0] == '#')
{
continue;
}
else if (!(iss >> eq >> val >> std::ws) || eq != "=" || iss.get() != EOF)
{
error = true;
}
if (error) { throw std::runtime_error("Parse error"); }
cfgMap[id] = std::stoi(val);
}
}
}
The file exists and when the parsing begin the content of the file is empty
The result of: line = stream.readLine(); is "".
If I add the file like a resource file and opened this way:
QFile cfgFile(":config.txt");
It's working correct, but the problem is that the config file is compiled and when you have to change some value, the project must be rebuild to take effect
I tried to compose the path like that QDir::currentPath + "/config.txt", but not working as well.
Another option is the undocumented feature "file_copies" in Qt 5.6 that you can use like this:
CONFIG += file_copies
configfiles.path = $$OUT_PWD
configfiles.files = $$PWD/config.txt
COPIES += configfiles
Found here: https://stackoverflow.com/a/54162789/6842395
If you don't like this method, that post has some more options to choose.
By the way, loooking at your ParseConfigFile() method seems that your config.txt is a collection of lines with the format: key = value, very similar to a classic INI file. Maybe you could use QSettings third constructor like this:
QSettings settings("config.txt", QSettings::IniFormat);
You can use QMAKE_EXTRA_TARGET in your profile like:
copyfile.commands += $${QMAKE_COPY} $$system_path($$PWD/config.txt) $$system_path($$DESTDIR/)
first.depends = $(first) copyfile
QMAKE_EXTRA_TARGETS += first copyfile
But ensure your $$DESTDIR is correct.

Changing file extensions while copying into a new sub directory C++

I have a directory that contains files (mostly images) and am trying to create a new sub directory named Thumbnails that will copy all images and replace the file extension from ".jpg" || ".png" to ".thumb".
So far my code will copy all the files into the new sub directory but I can't find a way to ignore all the files that are not images (it's copying every single file in the original directory) and to change the extension of the files that where copied.
#include <iostream>
#include <filesystem>
using namespace std;
namespace fs = std::experimental::filesystem;
//Predefined Functions
void createThumbnails(string path);
int main(void)
{
string DailyFolderPath = ".../images";
createThumbnails(DailyFolderPath);
return 0;
}
void createThumbnails(string path)
{
//Create Thumbnail Sub Directory
string newThumbDir = path + "/Thumbnails";
fs::create_directories(newThumbDir);
//Copy files into Thumbnail Directory
fs::copy(path, newThumbDir);
for (const auto & p : fs::directory_iterator(newThumbDir))
cout << p.path().filename() << endl;
}
Your attempt proposed in comment cannot work because you are passing only filenames instead the full paths to files:
fs::copy_file(p.path().filename(), p.path().filename().replace_extension(".thumb"));
^^^^^ your app doesn't see these files
You can try something like this:
void createThumbnails(string source)
{
fs::path newThumbDir = source + "/Thumbnails";
fs::create_directories(newThumbDir);
for (const auto & p : fs::directory_iterator(source)) // iterate over all files in source dir
{
fs::path fileToCopy = p; // make copy of path to source file
if (fileToCopy.extension() == ".png" || fileToCopy.extension() == ".jpg")
{
fileToCopy.replace_extension(".thumb"); // change extension
fs::path target = newThumbDir / fileToCopy.filename(); // targetDir/name.thumb
fs::copy_file(p,target); // pass full paths
}
}
}

QSettings: How to read array from INI file

I wanna read comma separated data form INI file. I've already read here:
QSettings::IniFormat values with "," returned as QStringList
How to read a value using QSetting if the value contains comma character
...that commas are treated as separators and QSettings value function will return QStringList.
However, my data in INI file looks like this:
norm-factor=<<eof
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
eof
I don't need a whole matrix. All rows joined up together are fair enough for me. But can QSettings handle such structure?
Should I read this using:
QStringList norms = ini->value("norm-factor", QStringList()).toStringList();
Or do I have to parse it in another way?
The line breaks are a problem since INI files use line breaks for their own syntax.
Qt seems to not support your type of line continuation (<<eol ... eol).
QSettings s("./inifile", QSettings::IniFormat);
qDebug() << s.value("norm-factor");
yields
QVariant(QString, "<<eof")
The <<eol expression might be invalid INI in itself. (Wikipedia on INI files)
I suggest you parse the file manually.
Ronny Brendel's answer is correct ...i am only adding code that solves above problem ...it creates temporary INI file with corrected arrays:
/**
* #param src source INI file
* #param dst destination (fixed) INI file
*/
void fixINI(const QString &src, const QString &dst) const {
// Opens source and destination files
QFile fsrc(src);
QFile fdst(dst);
if (!fsrc.open(QIODevice::ReadOnly)) {
QString msg("Cannot open '" + src + "'' file.");
throw new Exception(NULL, msg, this, __FUNCTION__, __LINE__);
}
if (!fdst.open(QIODevice::WriteOnly)) {
QString msg("Cannot open '" + dst + "'' file.");
throw new Exception(NULL, msg, this, __FUNCTION__, __LINE__);
}
// Stream
QTextStream in(&fsrc);
QTextStream out(&fdst);
bool arrayMode = false;
QString cache;
while (!in.atEnd()) {
// Read current line
QString line = in.readLine();
// Enables array mode
// NOTE: Clear cache and store 'key=' to it, without '<<eof' text
if (arrayMode == false && line.contains("<<eof")) {
arrayMode = true;
cache = line.remove("<<eof").trimmed();
continue;
}
// Disables array mode
// NOTE: Flush cache into output and remove last ',' separator
if (arrayMode == true && line.trimmed().compare("eof") == 0) {
arrayMode = false;
out << cache.left(cache.length() - 1) << "\n";
continue;
}
// Store line into cache or copy it to output
if (arrayMode) {
cache += line.trimmed() + ",";
} else {
out << line << "\n";
}
}
fsrc.close();
fdst.close();
}

C++/CLI: FileSystemWatcher move or copy non empty folder

I'am working with the FileSystemWatcher in C++/CLI. I'am having trouble with moving or copying a non empty folder: When copy a folder with one .txt file in it to the watching folder, then multiple createdand changed events are raised, that's fine, but when I move the same folder, only one single create event for the folder is raised. The problem is, that I need to know witch files are in it, so my idea was to just create a loop in the changed event that recursively searches trough the folder. This works for moving, but when I copy the folder, every event is raised twice.
I can't find an algorithm, so that only one create event for folders and files is raised.
Thanks for your help.
Code:
System::Void SnowDrive::Cloud::FileWatcher_Changed(System::Object^ sender, System::IO::FileSystemEventArgs^ e)
{
size_l FileSize;
string ServerInode,
FileName = c.marshal_as<std::string> (e -> Name),
FilePath = c.marshal_as<std::string> (e -> FullPath),
FtpPath = ToFtpPath (FilePath.substr (0, FilePath.find_last_of ("\\")));
if (FileName.find_last_of ("\\") != string::npos)
FileName = FileName.substr (FileName.find_last_of ("\\") + 1);
for (unsigned int i = 0; i < IgnoreFileList.size (); i++)
{
if (IgnoreFileList[i] == FilePath)
{
IgnoreFileList.erase (IgnoreFileList.begin () + i);
return;
}
}
if (!FileSystem::IsDir (FilePath))
{
FileSystem::FileSize (FilePath, &FileSize);
if (FileSize != 0)
IgnoreFileList.push_back (FilePath); // ignore twice changed events
// do something
}
else
{
if (sender -> ToString () != " ")
return;
DIR * Dir;
dirent * FindData;
if((Dir = opendir(FilePath.c_str ())) == NULL)
return;
while ((FindData = readdir(Dir)) != NULL)
{
FileName = FindData -> d_name;
if (FileName == string (".") || FileName == string (".."))
continue;
FileWatcher_Changed (gcnew String (" "), gcnew IO::FileSystemEventArgs (IO::WatcherChangeTypes::Created, gcnew String (FilePath.c_str ()), gcnew String (FileName.c_str ())));
}
}
}
System::Void SnowDrive::Cloud::FileWatcher_Created(System::Object^ sender, System::IO::FileSystemEventArgs^ e)
{
size_l FileSize;
string FilePath = c.marshal_as<std::string> (e -> FullPath);
if (!FileSystem::IsDir (FilePath))
{
FileSystem::FileSize (FilePath, &FileSize);
if (FileSize != 0)
IgnoreFileList.push_back (FilePath); // ignore twice changed events
}
FileWatcher_Changed (gcnew String (" "), e);
}
I don't know of a way to get only one Changed event like you want. I do, however, know how to get events for the moved files. You'll want to attach to the Renamed event.
Folder copy with files: One Changed event for the folder, and one per file.
Folder move with files: One Changed event for the folder, one Renamed event per file.