Is it necessary to flush a QTextStream before closing a QFile? - c++

I need to log some text messages to a file with following requirements :
Each text messages is written in a new line at the end of the file.
Be reasonably sure that each message was correctly written to the file.
So far, the function is using QTextStream and QFile:
bool FileManager::appendLine(const QString &line)
{
if(!m_file.open(QIODevice::Append | QIODevice::Text)) // m_file is a QFile
return false;
QTextStream ts(&m_file);
ts << line << endl;
bool status = (ts.status() == QTextStream::Ok);
m_file.close();
return status;
}
Point 1 is satisfied but i have doubts about Point 2.
Even Qt Doc says that it is sufficient to close() the QFile to flush all its internal buffers :
void QFileDevice::close()
Reimplemented from QIODevice::close().
Calls QFileDevice::flush() and closes the file. Errors from flush are ignored.
What about the internal buffer of the QTextStream ?
Is it necessary to call QTextStream::flush() before closing the file ?
About Point 2, i guess that reading back the line just after it has been written would be the only way to be 100% sure of that. (for example a power failure may occur while the kernel has still datas in its buffers )
Thanks.

In your case, its not, because you are appending &endl in each write!
Writting &endl to the QTextStream writes '\n' to the stream and flushes the stream. It is Equivalent to: stream << '\n' << flush;
Further, when QTextStream is flushed due to &endl, it will empty all data from its write buffer into the device and call flush() on the device.

While this particular code will work because operations with QTextStream end with an endl, it's still better to ensure that QTextStream is completely and utterly finished working with the file when you close it. Just use scopes.
bool FileManager::appendLine(const QString &line)
{
if(!m_file.open(QIODevice::Append | QIODevice::Text)) // m_file is a QFile
return false;
bool status {false};
{
QTextStream ts(&m_file);
ts << line << endl;
status = (ts.status() == QTextStream::Ok);
}
m_file.close();
return status;
}

Related

How can I prevent opening a QFile multiple time in parallel

I have written a function to check whether a file on disk is already in use. This is to avoid trying to execute a freshly downloaded installer while the antivirus is checking it, which fails.
The (generic) function looks like that:
bool isFileInUse(const QString& filePath)
{
QFile f(filePath);
if (!f.exists())
{
qDebug() << "File does not exist:" << filePath;
return false;
}
if (!f.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::ExistingOnly))
{
qDebug() << "File in use:" << filePath;
return true;
}
else
{
f.close();
qDebug() << "File free:" << filePath;
return false;
}
}
This works, I have tested manually with an installer (.exe) and it returns the expected result.
But now I want to write a unit test to check that function.
I have tried to create a file, and open it with QFile::open(QIODevice::WriteOnly), then call isFileInUse(..) on it, expecting to be already "in use", but it always returns false, i.e. Qt seem to have no problem to open twice the same file even in WriteOnly !
TEST(FilesUtils, isFileInUse)
{
QTemporaryDir dir;
const QString filePath = dir.filePath("test.txt");
createFile(filePath); // open, write dummy data and close the file
EXPECT_FALSE(FilesUtils::isFileInUse(filePath));
QFile f(filePath);
EXPECT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Append)); // OK
EXPECT_TRUE(FilesUtils::isFileInUse(filePath)); // FAILS, returns false
}
I have tried to open the file with a software like notepad.exe, and it also returns false.
Then I tried with Microsoft Word, and there it returns finally true (= file in use). But this is not portable and I cant expect a user to have Word installed on Windows, obviously.
Is there any way to open a file with Qt such that another QFile::open() returns false ? (i.e. lock the file)
Or does anyone sees something wrong in the code above ?
On Windows a file is opened for reading and/or writing and a share mode allowing additional reading/writing/deleting. This can create many interesting combinations, for example a file can be open for writing and allow additional open for reading but not writing. Or a file can be open for reading and allowing renames/deletes. See dwShareMode parameter of CreateFile WinAPI.
Unfortunately QFile::open API doesn't support share mode, since Qt is a portable framework and share mode exists only on Windows.
See these links with additional information for possible alternative solutions:
QLockFile
Qt: How to lock/prevent a file from being read while it is written?
Is possible to set QFile share permission?
Locking files using CreateFile on Windows
Once your target file has been created and opened, you should use the open() method with the QIODevice::NewOnly flag if it is to be called again.
QIODevice::NewOnly | 0x0040 | Fail if the file to be opened already exists. Create and open the file only if it does not exist. There is a guarantee from the operating system that you are the only one creating and opening the file. Note that this mode implies WriteOnly, and combining it with ReadWrite is allowed. This flag currently only affects QFile. Other classes might use this flag in the future, but until then using this flag with any classes other than QFile may result in undefined behavior. (since Qt 5.11)
Alternatively you could use QFile::isOpen() to test for prior file opening in function IsFileInUse:
if (f.isOpen()) return true;
Below is the code that proves the point (adapted from OP, which does not run out of the box):
#include <QString>
#include <QFile>
#include <QTemporaryDir>
#include <QtDebug>
#include <iostream>
void createFile(const QString& filePath)
{
// open, write dummy data and close the file
QFile f(filePath);
f.open(QIODevice::WriteOnly | QIODevice::Append);
f.write("dummy");
f.close();
}
#define EXPECT_FALSE(X) std::cerr << (X == false ? "OK, FALSE" : "NOT FALSE!") << std::endl;
#define EXPECT_TRUE(X) std::cerr << (X == true ? "OK, TRUE" : "NOT TRUE!") << std::endl;
class FilesUtils {
public:
static bool isFileInUse(const QString& filePath)
{
QFile f(filePath);
if (!f.exists())
{
qDebug() << "File does not exist:" << filePath;
return false;
}
if (!f.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::ExistingOnly))
{
qDebug() << "File in use:" << filePath;
return true;
}
else
{
f.close();
qDebug() << "File free:" << filePath;
return false;
}
}
};
int main()
{
QTemporaryDir dir;
const QString filePath = dir.filePath("test.txt");
if (QFileInfo(filePath).exists()) QFile(filePath).remove();
createFile(filePath); // open, write dummy data and close the file
EXPECT_FALSE(FilesUtils::isFileInUse(filePath));
QFile f(filePath);
EXPECT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Append)); // OK, returns true
EXPECT_TRUE(FilesUtils::isFileInUse(filePath)); // FAILS, returns false
f.close();
EXPECT_FALSE(f.open(QIODevice::WriteOnly | QIODevice::Append| QIODevice::NewOnly)); //OK, returns false
EXPECT_FALSE(FilesUtils::isFileInUse(filePath)); // SUCCEEDS, returns false
}
This code runs as expected:
File free: "/tmp/qt_temp-zCTbRf/test.txt"
OK, FALSE
OK, TRUE
File free: "/tmp/qt_temp-zCTbRf/test.txt"
NOT TRUE!
OK, FALSE
File free: "/tmp/qt_temp-zCTbRf/test.txt"
OK, FALSE

Converting valid QFile to QString - QString is empty

So I am trying to convert a QFile into a QString by doing the following:
void MainWindow::openTemplateFile(QString location)
{
if (location.isEmpty())
return;
else
{
QString variable;
templateFile.setFileName(location);
if (!templateFile.open(QFile::ReadOnly | QFile::Text))
{
QMessageBox::information(this, "Unable to open template",
templateFile.errorString());
return;
}
else // file opened and ready to read from
{
QTextStream in(&templateFile);
QString fileText = in.readAll();
qDebug() << templateFile.size() << in.readAll();
}
}
}
However, in I get the following result in the debug console:
48 ""
templateFile does exist and is part of the MainWindow class. This is also simplified code - in the actual program I read chars from the file and it works correctly. The location string is a result of the QFileDialog::getOpenFileName function, which I open a txt file with.
You call readAll() twice. The second time, the stream is positioned at end-of-file, and so readAll() has nothing to read and returns an empty string. Print fileText in your debug output instead.

Qt Creator combobox

How to write Combobox current text in a preexisting text file in hard drive? Here is my code:
void second::on_pushButton_4_clicked()
{
QFile file("vik.txt");
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
return;
QTextStream out(&file);
out << ui->comboBox_5->currentText() << "\n";
}
Maybe you forgot to close the file
void second::on_pushButton_4_clicked()
{
// Get comboBox text value
QString txt = ui->comboBox_5->currentText();
// Open file for writing
QFile file("vik.txt");
file.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream out(&file);
// Write in file
out << txt << "\n";
// Close file
file.close();
}
You have several issues in your code, let me enumerate them one-by-one:
You need this flag for "overwrite" as per your comment:
QIODevice::Truncate 0x0008 If possible, the device is truncated before it is opened. All earlier contents of the device are lost.
More importantly, have you checked whether the method returns after open with some error? If it does, please print out file.errorString() there:
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
qDebug() << file.errorString();
return;
}
On the most important note, you are likely facing the issue that the file is not in your current working directory. If the file resides beside the application executable, please change the corresponding line into your code to this:
QFile file(QCoreApplication::applicationDirPath() + "/vik.txt");

QFile and QTextStream for a logger class

I'm trying to create a Logger class using QFile and QTextStream but I can't find an efficient way of doing it. I just want to create a log(...) function in it.
I know it works if I do the following:
void CLogger::log(QString strLog,int nType) {
QFile file(m_strFileName);
file.open( QIODevice::Append | QIODevice::Text );
QTextStream logStream(&file);
logStream << nType << "-" << strLog;
file.close();
}
But It is quite nasty. I don't want to create a QFile object at every log line I insert.
So from that, I tried several different ways like:
1) (with QFile *m_pFile as member)
CLogger::CLogger()
{
m_pFile = new QFile(m_strFileName);
}
void CLogger::log(QString strLog,int nType)
{
m_pFile->open( QIODevice::Append | QIODevice::Text );
QTextStream logStream(m_pFile);
logStream << nType << "-" << strLog;
m_pFile.close();
}
or
2) (with QFile *m_pFile and QTextStream *m_pLogStream as members)
CLogger::CLogger()
{
m_pFile = new QFile(m_strFileName);
m_pFile->open( QIODevice::Append | QIODevice::Text );
m_pLogStream = new QTextStream(m_pFile);
}
void CLogger::log(QString strLog,int nType)
{
*m_pLogStream << nType << "-" << strLog;
}
In the first case, I get:
C2248: 'QTextStream::QTextStream' : cannot access private member
declared in class 'QTextStream'
in the 2nd, *m_pLogStream is not equivalent to a QTextStream&.
What am I doing wrong?
Actually it is not such a bad solution to open (and close) the log file every time you need to log something (unless you log 1000 times every second... but then noone would be able to process that amount of data ...). This not only allows you to have a very stable log (since you do not keep the file open all the time, so you are no depending on the flushing of the oeprating system), but also would allow you to be able to implement features as log rolling, and other niceties.
If you keep the log file open, in case of an unwanted "crash" you might not get all the log lines, depending of course how your OS is handling this ungraceful exits.
Here is a piece of code we use for logging:
QMutexLocker locker(&m_lineLoggerMutex);
QFile f(getLogFileName());
doRollLogsIfNeeded(static_cast<qint64>(f.size() + lineToBelogged.length()));
// Do not open in append mode but seek() to avoid warning for unseekable
// devices, note that if open is made with WriteOnly without Append, the
// file gets truncated
if (!f.open(QIODevice::ReadWrite | QIODevice::Text))
{
QTextStream out(stdout);
out << "CANNOT OPEN LOG FILE: " << getLogFileName();
return;
}
// seek() does nothing on sequential devices, this is in essence what QFile
// does when Append flag is set in open() but without warning (on Qt 4.8.3)
// However, Qt 4.8.1 issues the warning, so check it explicitly
if (!f.isSequential())
{
f.seek(f.size());
}
QTextStream out(&f);
out << lineToBelogged;
This goes in a method, and the destructors take care of the closing of the devices.

Using QTextStream to read a file that is being written to?

I currently read data from a file line by line like so
readFile(QFile *file)
{
QTextStream in(file);
while(!in.atEnd())
{
// Do stuff
}
}
I need to extend this to handle files that are currently being written to. The while loop should only exit if nothing has been written to the file for N seconds.
This should be easily implemented by adding in a timer and a sleep/wait loop, continuously checking if we are still atEnd(). However, the documentation is not very clear whether the return value of atEnd() will change if new data has been appended to the file.
If I use a QTextStream to read from a file that is being written to, will the return value of atEnd() accurately reflect the change in file contents?
will the return value of atEnd() accurately reflect the change in file contents?
Yes
You can test this by executing the following function:
void FileReader::_appendFileTest()
{
QFile file("/home/cklein/appendFile");
file.open(QIODevice::ReadOnly);
QTextStream in(&file);
while(true)
{
while(!in.atEnd())
{
QString line = in.readLine();
qDebug() << line;
}
SleepThread::msleep(1000);
}
}
Code for SleepThread taken from here. It just exposes the protected QThread::msleep() function.
And while the application is running, type this in the terminal
~ $ echo "foo" >> appendFile
~ $ echo "bar" >> appendFile
You can see that without resetting or re-opening the file, the atEnd() function returns a false when the file is appended to.