Creating a hash-value of a directory in Qt - c++

Is there any mean to determine if the directory content (including deep subdirectory structure) has changed since last access? I'm looking for a portable solution in C/C++, preferably in Qt.
P.S.:
If relevant, the background of the question.
In my application I have to scan recursively many directories and import some data in a database, when some conditions are true. Once the directory was imported, I mark it with a file ".imported" and ignore next times.
Now I want to mark also scanned but not imported directories. For this I'd store a file containing a directory hash. So, prior to scan I could compare the hash calculated with the last hash in file and skip scanning if they are equal.

There is a QFileSystemWatcher class that will notify you of changes.
If you want to create a Cryptographic Hash of a directory and its contents, this is how I do it: -
void AddToHash(const QFileInfo& fileInf, QCryptographicHash& cryptHash)
{
QDir directory(fileInf.absoluteFilePath());
directory.setFilter(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files);
QFileInfoList fileInfoList = directory.entryInfoList();
foreach(QFileInfo info, fileInfoList)
{
if(info.isDir())
{
// recurse through all directories
AddToHash(info, cryptHash);
continue;
}
// add all file contents to the hash
if(info.isFile())
{
QFile file(info.absoluteFilePath());
if(!file.open(QIODevice::ReadOnly))
{
// failed to open file, so skip
continue;
}
cryptHash.addData(&file);
file.close();
}
}
}
// create a fileInfo from the top-level directory
QFileInfo fileInfo(filePath);
QString hash;
// Choose an arbitrary hash, say Sha1
QCryptographicHash cryptHash(QCryptographicHash::Sha1);
// add all files to the hash
AddToHash(fileInfo, cryptHash);
// get a printable version of the hash
hash = cryptHash.result().toHex();

Related

Create a .zip folder and add files to it in C++ MFC

I am working on an C++ MFC project and one step is to create a large zip file with many other files inside it which may have different paths
As of right now, I have a vector that correctly stores all the file paths as CStrings to each file I will need to collect and add to the zip file
The way I am thinking to tackle this is by creating an empty zip file in a specified folder such as: C:\foo1\foo2\foo3 . The zip file would be titled foo4.zip, so its path would be C:\foo1\foo2\foo3\foo4
Then I would add each file one at a time by iterating through the vector containing the file paths with a for loop
Is it possible to create an empty zip file and add files to it ? If so, how ? Or should I create a regular folder, add files to it, then zip the folder after storing all files ? Or maybe I could create a zip file while supplying it the vector or file paths ?
Which is the better option in which situation and how do I accomplish this in C++ MFC ?
I have done some research on the ZipFile Class in Microsoft's Online Documentation but from my understanding, this Class expects all the files to be zipped to be in the same folder, but in my situation, these files are scattered across many different folders.
I use the ZipArchive library which is freely available here:
https://www.artpol-software.com/
It has excellent help documentation (via download) and you can create zips, add files to zips etc and has full progress monitoring support.
Used it for several years. Later on I will update my answer with sample code.
Single File
try
{
rZipArchive.AddNewFile(strFile, CZipCompressor::levelDefault, false);
}
catch (CZipException* e_)
{
const gsl::not_null<CZipException*> e{ e_ };
e->ReportError();
AfxMessageBox(e->GetErrorDescription(), MB_OK | MB_ICONERROR);
e->Delete();
}
Multiple Files
rZipArchive.AddNewFiles(strFullPath, strFileSpec, false, CZipCompressor::levelDefault, false);
Creating the Zip
Admittedly I have not included the progress callback class as it is fairly easy to write and would be bespoke to your application.
But this code gives you a flavour of what you can do:
try
{
// Create archive and set basic details
zipArchive.Open(strBackupPath, CZipArchive::zipCreate);
zipArchive.SetGlobalComment(_T("xxx"));
zipArchive.SetRootPath(theApp.GetWorkingPath());
/* ================================================ */
CMultiProgressCallback callback;
callback.SetProgressCtrl(&m_ProgressBackupRestore);
callback.SetProgressTextWnd(GetDlgItem(IDC_STATIC_PROGRESS_FILENAME));
callback.SetProgressPercentTextWnd(&m_lblProgressBackupRestore);
zipArchive.SetCallback(&callback,
CZipActionCallback::cbMultiAdd | CZipActionCallback::cbCalculateForMulti);
zipArchive.AddNewFile(strRegPath, CZipCompressor::levelDefault, false);
/* ================================================ */
// Build group filter
ZipArchiveLib::CGroupFileFilter groupFilter;
// Files to include
groupFilter.Add(std::make_unique<ZipArchiveLib::CNameFileFilter>(_T("*.*")).release());
// Files to exclude
groupFilter.Add(std::make_unique<ZipArchiveLib::CNameFileFilter>(_T("exp*.htm"), true).release()); // Preview in Browser
groupFilter.Add(std::make_unique<ZipArchiveLib::CNameFileFilter>(_T("mwe*.xml"), true).release()); // Meeting Workbook Editor
groupFilter.Add(std::make_unique<ZipArchiveLib::CNameFileFilter>(_T("mwp*.xml"), true).release()); // Meeting Workbook Print
groupFilter.Add(std::make_unique<ZipArchiveLib::CNameFileFilter>(_T("sre*.xsl"), true).release()); // Sound Rota Editor
groupFilter.Add(std::make_unique<ZipArchiveLib::CNameFileFilter>(_T("srt*.xsl"), true).release()); // Sound Rota Temp
groupFilter.Add(std::make_unique<ZipArchiveLib::CNameFileFilter>(_T("gcal_*.tmp"), true).release()); // Google Calendar Temp
groupFilter.Add(std::make_unique<ZipArchiveLib::CNameFileFilter>(_T("*.bin3"), true).release()); // Outlook OAuth Cache
zipArchive.AddNewFiles(theApp.GetWorkingPath(), groupFilter, false, CZipCompressor::levelDefault, false);
/* ================================================ */
/* ================================================ */
// AJT v20.1.7 — Backup mode
if (eBackupWhat == COtherSettingsAutomaticBackupPage::EnumBackupWhat::Complete)
{
// Reset
groupFilter.Clear();
// Files to include
groupFilter.Add(std::make_unique<ZipArchiveLib::CNameFileFilter>(_T("*.*")).release());
// Files to exclude
groupFilter.Add(std::make_unique<ZipArchiveLib::CNameFileFilter>(_T("exp*.htm"), true).release()); // Preview in Browser
groupFilter.Add(std::make_unique<ZipArchiveLib::CNameFileFilter>(_T("mwa*.xml"), true).release()); // Meeting Workbook Assignment slips
zipArchive.AddNewFiles(theApp.GetWorkingPath() + _T("AssignmentSlips"),
groupFilter, // Files to include
true, // Recursive
CZipCompressor::levelDefault, // Compression level
false); // Skip initial path
}
/* ================================================ */
auto zipCloseResult = zipArchive.Close();
}
catch (CZipException* e_)
{
const gsl::not_null<CZipException*> e{ e_ };
zipArchive.Close(CZipArchive::afAfterException);
e->ReportError();
bExported = false;
e->Delete();
}
catch (CException *e_)
{
const gsl::not_null<CException*> e{ e_ };
zipArchive.Close(CZipArchive::afAfterException);
// Problem - tell user
e->ReportError();
bExported = false;
e->Delete();
}
if (bExported)
AfxMessageBox(IDS_STR_SETTINGS_SAVED, MB_OK | MB_ICONINFORMATION);
}
The ZipArchive library does have a commercial version where you pay for a license but in my case I did not need to purchase one. This is what it says:
The open source version of the ZipArchive Library is licensed under GNU GPL.
When you download the library you need to build the right version to create your required LIB or DLL for use with your application. Several Visual Studio platform files are provided. Just pick the one you need.

Qt C++: QFile won't open *.txt resource in WriteOnly [duplicate]

I have this code in QT c++
void writeInFile()
{
QFile file(":/texts/test.txt");
if(file.open(QIODevice::ReadWrite))
{
QTextStream in(&file);
in<<"test";
}
file.close();
}
I want to add "test" to my text file which is in resources with prefix "texts", but this function does nothing, I can't write or read from file when I am oppening it with "QIODevice::ReadWrite" or "QFile::ReadWrite", I can only read from it on readonly mode. Any help or advice welcome.
Qt resource files are read-only, as they are put into the binary as "code" - and the application cannot modify itself.
Since editing resources is simply impossible, you should follow the standard approach of caching those files. This means you copy the resource to the local computer and edit that one.
Here is a basic function that does exactly that:
QString cachedResource(const QString &resPath) {
// not a ressource -> done
if(!resPath.startsWith(":"))
return resPath;
// the cache directory of your app
auto resDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
auto subPath = resDir + "/resources" + resPath.mid(1); // cache folder plus resource without the leading :
if(QFile::exists(subPath)) // file exists -> done
return subPath;
if(!QFileInfo(subPath).dir().mkpath("."))
return {}; //failed to create dir
if(!QFile::copy(resPath, subPath))
return {}; //failed to copy file
// make the copied file writable
QFile::setPermissions(subPath, QFileDevice::ReadUser | QFileDevice::WriteUser);
return subPath;
}
In short, it copies the resource to a cache location if it does not already exist there and returns the path to that cached resource. One thing to be aware of is that the copy operation presevers the "read-only" permission, which means we have to set permissions manually. If you need different permissions (i.e. execute, or access for the group/all) you can adjust that line.
In your code, you would change the line:
QFile file(cachedResource(":/texts/test.txt"));

QT ; How can I store resources path to variable at run-time?

My qt project has .qrc file so my resources files are stored like ":/audio/melody/...".
I need to choose which files to use or not in runtime, so my program stores the resources path in .txt file.
In runtime, my program get these path to string, as variables.
So now I need to use these variables to put [setSource(variables)] methods of any other Qt objects. But it can't.
I tried to convert std::string (which has the file path) to QString, and put it in QtObj.setSource() as QUrl(QString).
But I found the QUrl(QString) has no data(I expected that there was resource path like ":/audio/melody/.. blahblah" in QUrl(QString)).
How can I convert the std::string(which has resource path) to QUrl, in order to use it as a resource path?
Actually, I wonder there is possibility to use resource path as variables.
To get a list of all resources, the files itself and a URL to the file at runtime you can do this:
QHash<QUrl, QFile> list;
QDirIterator it(":", QDirIterator::Subdirectories);
while (it.hasNext()) {
QFile file(it.next());
QUrl url = QUrl::fromLocalFile(file.fileName())
list.insert(url, file);
}

Writting in text file error, QT

I have this code in QT c++
void writeInFile()
{
QFile file(":/texts/test.txt");
if(file.open(QIODevice::ReadWrite))
{
QTextStream in(&file);
in<<"test";
}
file.close();
}
I want to add "test" to my text file which is in resources with prefix "texts", but this function does nothing, I can't write or read from file when I am oppening it with "QIODevice::ReadWrite" or "QFile::ReadWrite", I can only read from it on readonly mode. Any help or advice welcome.
Qt resource files are read-only, as they are put into the binary as "code" - and the application cannot modify itself.
Since editing resources is simply impossible, you should follow the standard approach of caching those files. This means you copy the resource to the local computer and edit that one.
Here is a basic function that does exactly that:
QString cachedResource(const QString &resPath) {
// not a ressource -> done
if(!resPath.startsWith(":"))
return resPath;
// the cache directory of your app
auto resDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
auto subPath = resDir + "/resources" + resPath.mid(1); // cache folder plus resource without the leading :
if(QFile::exists(subPath)) // file exists -> done
return subPath;
if(!QFileInfo(subPath).dir().mkpath("."))
return {}; //failed to create dir
if(!QFile::copy(resPath, subPath))
return {}; //failed to copy file
// make the copied file writable
QFile::setPermissions(subPath, QFileDevice::ReadUser | QFileDevice::WriteUser);
return subPath;
}
In short, it copies the resource to a cache location if it does not already exist there and returns the path to that cached resource. One thing to be aware of is that the copy operation presevers the "read-only" permission, which means we have to set permissions manually. If you need different permissions (i.e. execute, or access for the group/all) you can adjust that line.
In your code, you would change the line:
QFile file(cachedResource(":/texts/test.txt"));

QDir currentPath and cd() not working?

I am currently attempting to create a new directory and create a new file inside of this directory. However, QDir recognizes that this file exists, however when I try to cd to my new directory, the currentPath returns the same value before and after the QDir().cd(dirName)
QDir().cdUp();
if(!QDir(dirName).exists())
QDir().mkdir(dirName);
qDebug() << QDir().currentPath(); // returns a path up from exe dir
if(QDir().cd(dirName))
qDebug() << QDir().currentPath(); //returns the same path as above
Really not sure why this isn't working, I am pretty new to programming and was wondering why this was.
QDir().cd(dirName)
Every time you perform QDir() you're creating a new instance of the object, then you perform an operation on it (i.e., .cd(dirName)), and finally that object goes out of scope and is destroyed; thereby losing all your changes.
Instead you should be creating a single instance and performing all operations on it.
QDir dir;
dir.cd(dirName);
dir.path();
The constructor QDir() creates a QDir object pointing to the program's working directory. QDir()::cd() changes that QDir object directory, however it does not change program directory. If you really want to change current application working directory, see QDir()::setCurrent(const QString & path)
That current application directory is used as relative path for files. So, to create a file in a new directory, you can specify the full file path or to use relative path as:
QDir::setCurrent(new_base_path);
QFile("some_relative_file_name");
...