fopen with const char * from QString not possible - c++

To dynamically open a FILE I am passing a QString full path.
If passed as a variable, the code fails.
If entered directy (not via a variable) everything works just fine. What is going on here?
QString outputfile_qstring("C:/temp/out.mp3");
qDebug()<<"Original output file " << outputfile_qstring;
const char* outputfile = outputfile_qstring.toLatin1().constData();
qDebug()<<"Trying to open output file " << outputfile;
fout = fopen(outputfile, "wb+");
bool fileIsOpen = (fout != 0);
if ( !fileIsOpen ){
errStr_ = "Error opening the output file " + outputfile_qstring;
Q_ASSERT(false && "Could not open output file");
return false;
}
The QString to const char * conversion always fails.
Original output file "C:/temp/out.mp3"
Trying to open output file ????????????????????????aSC,_??r

The problem is here:
const char* outputfile = outputfile_qstring.toLatin1().constData();
The toLAtin1 function returns a QByteArray by value. And since you don't save that object, it will be destructed once the expression is finished, leaving you with outputfile being an invalid pointer to non-existing data.
The simple solution is to use the expression outputfile_qstring.toLatin1().constData() directly in the call to fopen. Or not use fopen and the C file functions at all and only use Qt files.

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

Passing a char array reading from file to fopen() generating error as Invalid arguments

I tried to read file path and name into a char array from a configuration.txt file. Then passing the char array to fopen(). However, it is generating errors like invalid argument. Instead, if I directly passed the path same to what is in configuration.txt, it will succeed. The code is like:
FILE *FIDConfig = fopen(argv[1],"r"); // open configuration file; could be a .txt file
char inputfname[200], backgroundfname[200], outputfname[200];
fscanf(FIDConfig, "%s", inputfname); //int fscanf ( FILE * stream, const char * format, ... );
fscanf(FIDConfig, "%s", backgroundfname);
fscanf(FIDConfig, "%s", outputfname);
/* FILE WITH THE RAW DATA */
errno = 0;
FILE *FIDIN, *FIDOUT, *FIDBack; // inputfname = "./data.bin", backgroundfname = "./dbk.bin";
FIDIN = fopen(inputfname, "rb"); // if changed to FIDIN("./data.bin","rb") it works!
FIDBack = fopen(backgroundfname, "rb");

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?

How to create a dynamic filename for an SD card on Arduino

I'd like to log my data on my Arduino one file at a time. I'd like the filename to be a combination of the number of milliseconds that have passed + some ID. For example, GPS data would be millis()+"GPS".
I tried the following code, but it doesn't like the fact that I am using a String. I could use a char array, but the length would always be dynamic. Is there a way to do this with at string somehow?
static void writeToSD()
{
String logEntry = " GPS: ";
logEntry += GPSString;
String filename = String(millis());
filename += "GPS";
Serial.println(logEntry);
Serial.println(filename);
File dataFile = SD.open(filename, FILE_WRITE);
// If the file is available, write to it:
if (dataFile) {
dataFile.println(logEntry);
dataFile.close();
Serial.println("Closed");
}
// If the file isn't open, pop up an error:
else {
Serial.println("error opening file");
}
}
You could try the following
char fileNameCharArray[filename.length()];
filename.toCharArray(fileNameCharArray, filename.length())
File dataFile = SD.open(fileNameCharArray, FILE_WRITE);
sprintf (filename, "%ld-GPS", millis());
Note that the use of String on Arduino is discouraged because of the well documented memory leak/fragmentation problems.