How to add non compilable configuration file into QT Project? - c++

I have a .txt file in QT project, where I store settings parameters for my app. If I read it that way
void MainWindow::ReadApplicationSettings()
{
QFile cfgFile("config.txt");
if(!cfgFile.exists())
{
qDebug() << "Cannot find config file.";
}
else
{
ParseConfigFile(cfgFile);
SetCfg();
}
cfgFile.close();
}
And Parse it:
void MainWindow::ParseConfigFile(QFile &cfgFile)
{
QString line;
if (cfgFile.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream stream(&cfgFile);
while (!stream.atEnd())
{
line = stream.readLine();
std::istringstream iss(line.toStdString());
std::string id, eq, val;
bool error = false;
if (!(iss >> id))
{
error = true;
}
else if (id[0] == '#')
{
continue;
}
else if (!(iss >> eq >> val >> std::ws) || eq != "=" || iss.get() != EOF)
{
error = true;
}
if (error) { throw std::runtime_error("Parse error"); }
cfgMap[id] = std::stoi(val);
}
}
}
The file exists and when the parsing begin the content of the file is empty
The result of: line = stream.readLine(); is "".
If I add the file like a resource file and opened this way:
QFile cfgFile(":config.txt");
It's working correct, but the problem is that the config file is compiled and when you have to change some value, the project must be rebuild to take effect
I tried to compose the path like that QDir::currentPath + "/config.txt", but not working as well.

Another option is the undocumented feature "file_copies" in Qt 5.6 that you can use like this:
CONFIG += file_copies
configfiles.path = $$OUT_PWD
configfiles.files = $$PWD/config.txt
COPIES += configfiles
Found here: https://stackoverflow.com/a/54162789/6842395
If you don't like this method, that post has some more options to choose.
By the way, loooking at your ParseConfigFile() method seems that your config.txt is a collection of lines with the format: key = value, very similar to a classic INI file. Maybe you could use QSettings third constructor like this:
QSettings settings("config.txt", QSettings::IniFormat);

You can use QMAKE_EXTRA_TARGET in your profile like:
copyfile.commands += $${QMAKE_COPY} $$system_path($$PWD/config.txt) $$system_path($$DESTDIR/)
first.depends = $(first) copyfile
QMAKE_EXTRA_TARGETS += first copyfile
But ensure your $$DESTDIR is correct.

Related

Add new Directory with Poco::zip always give Exception

I'm building a Compress/Decompress functions and currently, I only able to compress 1 single file into a zip file. Each time I add a new Directory or add Recursively a Directory it always give me Exception.
Here is my ZipFile function:
Poco::DateTime(Timestamp);
set <string> extensionsSet;
std::ofstream fos(target, ios::binary);
Poco::Zip::Compress c(fos, true);
for (int i = 0; i < extensions.Size(); i++) {
string ext = std::dynamic_pointer_cast<String>(extensions.operator[](i))->GetPrimitive();
extensionsSet.insert(ext);
}
c.setStoreExtensions(extensionsSet);//set extensions List
Poco::File aFile(source);
if (aFile.exists())
{
Poco::Path p(aFile.path());
if (aFile.isDirectory())
{
Poco::Path sourceDir(source);
Poco::Path targetDir(target);
c.addDirectory(targetDir, Poco::DateTime(Timestamp));// give exception
targetDir.makeDirectory();
c.addRecursive(sourceDir);
}
else if (aFile.isFile())
{
c.addFile(p, p.getFileName());
}
}
else {
_log.EndMethod();
throw new FileNotFoundException("File Not Found");
}
c.close(); // MUST be done to finalize the Zip file
fos.close();
Here is where my exception, the underneath block of code is within addDirectory methods of Poco:
if (!ZipCommon::isValidPath(fileStr))
throw ZipException("Illegal entry name " + fileStr + " containing parent directory reference");
There is a member in "Poco::Path" called "absolute".
You can set absolute to false by constructor
otherwise the ZipException is thrown because this function returns "false":
bool ZipCommon::isValidPath(const std::string& path)
{
try
{
if (Path(path, Path::PATH_UNIX).isAbsolute() || Path(path, Path::PATH_WINDOWS).isAbsolute())
return false;
}
...
}
Here is a example:
std::ofstream fos(target, ios::binary);
Poco::Zip::Compress compress(fos, true);
std::vector<std::string> dirs; // parent dirs in zip file
Poco::Path myPath(false); // set path to non-absolute
// add parent directories
for (auto& dir : dirs)
{
myPath.pushDirectory(dir);
}
//add file
myPath.setFileName("FileName.txt");
//add file to archive
compress.addFile(theFile, relativePath);
//close archive and get zipArchiveObject
ZipArchive zipArchive(compress.close());

ifstream - monitor updates to file

I am using ifstream to open a file and read line by line and print to console.
Now, I also want to make sure that if the file gets updated, it reflects. My code should handle that.
I tried setting fseek to end of the file and then looking for new entries by using peek. However, that did not work.
Here's some code I used
bool ifRead = true;
while (1)
{
if (ifRead)
{
if (!file2read.eof())
{
//valid file. not end of file.
while (getline(file2read, line))
printf("Line: %s \n", line.c_str());
}
else
{
file2read.seekg(0, file2read.end);
ifRead = false;
}
}
else
{
//I thought this would check if new content is added.
//in which case, "peek" will return a non-EOF value. else it will always be EOF.
if (file2read.peek() != EOF)
ifRead = true;
}
}
}
Any suggestions on what could be wrong or how I could do this.

QT Can't read from a file

I am trying to open a file from a specific location and it seems to find the path properly, but I can't figure out why it always skips the while loop.
QString utm_file_loc = "C:\\Example\\test\\UTM_Zone.config";
QFile fileutm(utm_file_loc);
QTextStream utm_in(&fileutm);
QString value;
while(!utm_in.atEnd())
{
QString line = utm_in.readLine();
line.replace(" ", "");
if( (line.indexOf("#") <0 || 1 < line.indexOf("#")) &&
(line.contains("UTM_ZONE=")) )
{
value = line.mid(line.indexOf("=")+1);
break;
}
}
The config file is 1 line and contains UTM_ZONE = 17
I thought it might have to do with it being 1 line and so it always thinks it's at the end, but I tried adding more lines before and after to the file and it still skips the loop.
Between the line where you make the File object and the line where you pass it into the QTextStream, you need to open the file:
if ( fileutm.open(QIODevice::ReadOnly) )
{
//Create you QTextStream and use it here...
}
else
{
//Report error opening file here....
}

QSettings: How to read array from INI file

I wanna read comma separated data form INI file. I've already read here:
QSettings::IniFormat values with "," returned as QStringList
How to read a value using QSetting if the value contains comma character
...that commas are treated as separators and QSettings value function will return QStringList.
However, my data in INI file looks like this:
norm-factor=<<eof
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0
eof
I don't need a whole matrix. All rows joined up together are fair enough for me. But can QSettings handle such structure?
Should I read this using:
QStringList norms = ini->value("norm-factor", QStringList()).toStringList();
Or do I have to parse it in another way?
The line breaks are a problem since INI files use line breaks for their own syntax.
Qt seems to not support your type of line continuation (<<eol ... eol).
QSettings s("./inifile", QSettings::IniFormat);
qDebug() << s.value("norm-factor");
yields
QVariant(QString, "<<eof")
The <<eol expression might be invalid INI in itself. (Wikipedia on INI files)
I suggest you parse the file manually.
Ronny Brendel's answer is correct ...i am only adding code that solves above problem ...it creates temporary INI file with corrected arrays:
/**
* #param src source INI file
* #param dst destination (fixed) INI file
*/
void fixINI(const QString &src, const QString &dst) const {
// Opens source and destination files
QFile fsrc(src);
QFile fdst(dst);
if (!fsrc.open(QIODevice::ReadOnly)) {
QString msg("Cannot open '" + src + "'' file.");
throw new Exception(NULL, msg, this, __FUNCTION__, __LINE__);
}
if (!fdst.open(QIODevice::WriteOnly)) {
QString msg("Cannot open '" + dst + "'' file.");
throw new Exception(NULL, msg, this, __FUNCTION__, __LINE__);
}
// Stream
QTextStream in(&fsrc);
QTextStream out(&fdst);
bool arrayMode = false;
QString cache;
while (!in.atEnd()) {
// Read current line
QString line = in.readLine();
// Enables array mode
// NOTE: Clear cache and store 'key=' to it, without '<<eof' text
if (arrayMode == false && line.contains("<<eof")) {
arrayMode = true;
cache = line.remove("<<eof").trimmed();
continue;
}
// Disables array mode
// NOTE: Flush cache into output and remove last ',' separator
if (arrayMode == true && line.trimmed().compare("eof") == 0) {
arrayMode = false;
out << cache.left(cache.length() - 1) << "\n";
continue;
}
// Store line into cache or copy it to output
if (arrayMode) {
cache += line.trimmed() + ",";
} else {
out << line << "\n";
}
}
fsrc.close();
fdst.close();
}

C++/CLI: FileSystemWatcher move or copy non empty folder

I'am working with the FileSystemWatcher in C++/CLI. I'am having trouble with moving or copying a non empty folder: When copy a folder with one .txt file in it to the watching folder, then multiple createdand changed events are raised, that's fine, but when I move the same folder, only one single create event for the folder is raised. The problem is, that I need to know witch files are in it, so my idea was to just create a loop in the changed event that recursively searches trough the folder. This works for moving, but when I copy the folder, every event is raised twice.
I can't find an algorithm, so that only one create event for folders and files is raised.
Thanks for your help.
Code:
System::Void SnowDrive::Cloud::FileWatcher_Changed(System::Object^ sender, System::IO::FileSystemEventArgs^ e)
{
size_l FileSize;
string ServerInode,
FileName = c.marshal_as<std::string> (e -> Name),
FilePath = c.marshal_as<std::string> (e -> FullPath),
FtpPath = ToFtpPath (FilePath.substr (0, FilePath.find_last_of ("\\")));
if (FileName.find_last_of ("\\") != string::npos)
FileName = FileName.substr (FileName.find_last_of ("\\") + 1);
for (unsigned int i = 0; i < IgnoreFileList.size (); i++)
{
if (IgnoreFileList[i] == FilePath)
{
IgnoreFileList.erase (IgnoreFileList.begin () + i);
return;
}
}
if (!FileSystem::IsDir (FilePath))
{
FileSystem::FileSize (FilePath, &FileSize);
if (FileSize != 0)
IgnoreFileList.push_back (FilePath); // ignore twice changed events
// do something
}
else
{
if (sender -> ToString () != " ")
return;
DIR * Dir;
dirent * FindData;
if((Dir = opendir(FilePath.c_str ())) == NULL)
return;
while ((FindData = readdir(Dir)) != NULL)
{
FileName = FindData -> d_name;
if (FileName == string (".") || FileName == string (".."))
continue;
FileWatcher_Changed (gcnew String (" "), gcnew IO::FileSystemEventArgs (IO::WatcherChangeTypes::Created, gcnew String (FilePath.c_str ()), gcnew String (FileName.c_str ())));
}
}
}
System::Void SnowDrive::Cloud::FileWatcher_Created(System::Object^ sender, System::IO::FileSystemEventArgs^ e)
{
size_l FileSize;
string FilePath = c.marshal_as<std::string> (e -> FullPath);
if (!FileSystem::IsDir (FilePath))
{
FileSystem::FileSize (FilePath, &FileSize);
if (FileSize != 0)
IgnoreFileList.push_back (FilePath); // ignore twice changed events
}
FileWatcher_Changed (gcnew String (" "), e);
}
I don't know of a way to get only one Changed event like you want. I do, however, know how to get events for the moved files. You'll want to attach to the Renamed event.
Folder copy with files: One Changed event for the folder, and one per file.
Folder move with files: One Changed event for the folder, one Renamed event per file.