Infinite cycle due to QTextStream - c++

So, I get infinite cycle while trying to read lines from file (line by line). I was trying to use do{}while(); cycle like that:
QTextStream stream(stdin);
QString line;
do {
line = stream.readLine();
} while (!line.isNull());
but I get empty string.
Sure, I checked file path (it is right). I was trying to use /Users/user/tts.txt path but without changes. I was trying to read other files (like m3u). And it's not working on macOS Catalina, Windows 10, Linux (Debian).
So, why did I get infinite cycle?
QStringList Manager::GetLinesFromFile(const QString &nameOfFile)
{
QStringList lines = {};
//path to file
const QString path = QCoreApplication::applicationDirPath() + "/bin/" + "tts.txt";
//"/Users/user/tts.txt"
QFile buffer;
buffer.QFile::setFileName(path);
#ifndef Q_DEBUG
qDebug() << path;
#endif
if(buffer.QFile::exists())
{
if(!buffer.QIODevice::open(QIODevice::ReadOnly))
{
#ifndef Q_DEBUG
qCritical() << "error: can't open file";
#endif
}
else
{
QTextStream stream(&buffer);
// both conditions
// (!stream.QTextStream::atEnd())
while(!buffer.QFileDevice::atEnd())
lines.QList::push_back(stream.QTextStream::readLine());
buffer.QFile::close();
}
}
else
{
#ifndef Q_DEBUG
qCritical() << "error: file not exists";
#endif
}
return lines;
}

Have a look at the QTextstream documentation https://doc.qt.io/qt-5/qtextstream.html. There is an example of reading line by line. Your while loop should read until the stream reaches the end of the buffer and many of the in built read functions will return false when his happens

So, I got it. I opened the file incorrectly.
I was using:
if(!file.QIODevice::open(QIODevice::ReadOnly))
but it should be like that:
if(!file.QFile::open(QFile::ReadOnly))

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

Is it necessary to flush a QTextStream before closing a QFile?

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

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 - Parsing through CSV file

OK, so I am using QT Creator for C++ and I am making a function that allows me to parse through the CSV file that I have named getExcelFile. Everything else is working fine but my code will not enter my while loop for some reason which is driving me crazy. Some suggestions would be helpful! Thanks.
void Widget::getExcelFile(){
//Name of the Qfile object.
//filename is the directory of the file once it has been selected in prompt
QFile thefile(filename);
//If the file isn't successfully open
if (thefile.open(QIODevice::ReadOnly | QIODevice::Text)){
qDebug() << "File opened successfully";
//Converts text file to stream
QTextStream in(&thefile);
fileContent=in.readAll();
QString line;
while (!in.atEnd()){
//line = textStream.readLine();//reads line from file
//Will not enter this loop for some odd reason.
qDebug() << "This text does not print out";
}
}
qDebug() << "This prints out successfully";
ui->textEdit->setPlainText(fileContent);
}
You did in.readAll(), after that call in.atEnd() will return true. Either remove in.readlAll() or while loop, why do you need both?

Qt reading file and mapping to QVector very slow (crashes)

So what I am trying to do is read a file and map it to a two dimensional QVector. Here is my code so far
void dataModel::parseFileByLines()
{
QVector<QVector<QString> > dataSet;
lastError = "";
QRegExp reg(fileDelimiter);
QFile inFile(inputFile);
if (inFile.open(QIODevice::ReadOnly)){
QTextStream fread(&inFile);
long totalSize = inFile.size();
QString line;
while(!fread.atEnd()){
line = fread.readLine();
dataSet.append(line.split(reg,QString::KeepEmptyParts).toVector());
}
}else{
lastError = "Could not open "+inputFile+" for reading";
}
}
My issue is that when dealing with 1000,000 lines or more the program crashes with a message saying "This application has requested the Runtime to terminate it in an unusual way". Is there a more efficient way I can achieve my goal ? If so how ?
The input file may be in a format like so
ID,NAME,AGE,GENDER...etc
1,Sam,12
...
...
1000000
I would really appreciate any help or advice
I have tested this (QList) version here on my computer and it runs much faster then the QVector version and I also believe it will not crash.
void parseFileByLines(QString inputFile)
{
QList<QList<QString> > dataSet;
QString lastError = "";
QFile inFile(inputFile);
if (inFile.open(QIODevice::ReadOnly)){
QTextStream fread(&inFile);
long totalSize = inFile.size();
QString line;
while(!fread.atEnd()){
line = fread.readLine();
QList<QString> record = line.split('\t',QString::KeepEmptyParts);
dataSet.append(record);
}
}else{
lastError = "Could not open "+inputFile+" for reading";
}
}