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

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"));

Related

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");
...

How to access Qt resource data from non-Qt functions

As I understand it, the way to packages non-code resources such as data files in a Qt app is using the resource system. However, what if I want to access a resource using a non-Qt function. For example, I may have a .txt or .csv file with some application data that I want to accessing using ifstream. It doesn't seem to work to use the ": ..." syntax in place of a filename for non-Qt functions and classes. Is there a separate workflow for packaging data used by non-Qt functions in an app?
I'm using OSX, but I would assume these issues are platform independent.
The sole purpose of the Qt resource system is to bundle data within the executable itself. If you wish not to integrate the data in the executable, then you simply must not use the resource system.
On mac, if you wish to add "data.txt" from project source to your application bundle, but not to the executable itself, add the following to your .pro file:
mac {
BUNDLE = $$OUT_PWD/$$TARGET$$quote(.app)/Contents
QMAKE_POST_LINK += ditto \"$$PWD/data.txt\" \"$$BUNDLE/Resources/\";
}
Given the above project file, use the QCoreApplication::applicationDirPath() for a path useful in getting to the file:
#include <QCoreApplication>
#include <QFile>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << QCoreApplication::applicationDirPath();
QFile data(QCoreApplication::applicationDirPath() + "/../Resources/data.txt");
if (data.open(QIODevice::ReadOnly | QIODevice::Text))
qDebug() << data.readAll();
return 0;
}
In the above example, the Resources folder has nothing to do with the Qt resource system. It's simply a naming convention in OS X application bundles. We're not using the Qt resource system here.
If you wish to use the Qt resource system and access the resource data directly and not through a QFile, the QResource class provides access to resources that are bundled in the executable.
If the code under your control insists on using ifstream for data input, then it's artificially limited and should be fixed. It should use istream instead, as that class can be backed by anything, not necessarily a file. If it's code that you don't control, you could set up the ifstream on a QLocalSocket.
You can map the constant QResource::data() to an input stream via a stream buffer.
If the resource isCompressed(), then you need to first decompress it to a temporary area. You can also disable resource compression to avoid the decompression step. You can use a whole-executable compressor like upx instead - by the time your code runs, everything will be already decompressed and ready to use.
You can copy the resource file into a temporary folder. To do this, use a QTemporaryDir which creates a temporary folder and deletes it automatically when the program is finished. To access the path of that folder, use the QTemporaryDir::path() method. Here is an example of how you can use it:
#include <QTemporaryDir> //You need to include this header
QTemporaryDir temporaryDir;
//Copy the resource file into the temporary folder
QFile::copy(":/exampleprefix/examplefile.txt", temporaryDir.path() + "/examplefile.txt");
//Read the file
std::ifstream fileStream(QString(temporaryDir.path() + "/examplefile.txt").toLatin1().data());
//etc
What about opening the resource file with a QFile object, wrapping this with a QDataStream object, and wrapping this with a boost::iostreams::stream object, which derives from a specialization of std::basic_istream? Sounds complicated, but does not need too many lines of code, see this answer.

Creating a hash-value of a directory in Qt

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();