QDir mkpath returns true, but directory not created - c++

I'm having a weird issue with creating paths using Qt on Linux. I've written a standalone test program that creates paths and tests for their existence. This works as expected and creates the directory.
/* make path */
QString p("/usr2/archive/S1234ABC/5/6");
QDir d;
if (d.mkpath(p)) qDebug() << "mkpath() returned true";
else qDebug() << "mkpath() returned false";
QDir d2;
if (d2.exists(p)) qDebug() << "exists() returned true";
else qDebug() << "exists() returned false";
I made that test example into a more robust function, in another project. But it isn't working... mkpath() and exists() return true, but the paths don't exist on the hard disk.
bool nidb::MakePath(QString p, QString &msg) {
if ((p == "") || (p == ".") || (p == "..") || (p == "/") || (p.contains("//")) || (p == "/root") || (p == "/home")) {
msg = "Path is not valid [" + p + "]";
return false;
}
WriteLog("MakePath() called with path ["+p+"]");
QDir path;
if (path.mkpath(p)) {
WriteLog("MakePath() mkpath returned true [" + p + "]");
if (path.exists()) {
WriteLog("MakePath() Path exists [" + p + "]");
msg = QString("Destination path [" + p + "] created");
}
else {
WriteLog("MakePath() Path does not exist [" + p + "]");
msg = QString("Unable to create destination path [" + p + "]");
return false;
}
}
else {
msg = QString("MakePath() mkpath returned false [" + p + "]");
return false;
}
return true;
}
The output from my program:
[2019/06/04 13:19:37][26034] MakePath() called with path [/usr2/archive/S0836VYL/6/5/dicom]
[2019/06/04 13:19:37][26034] MakePath() mkpath returned true [/usr2/archive/S0836VYL/6/5/dicom]
[2019/06/04 13:19:37][26034] MakePath() Path exists [/usr2/archive/S0836VYL/6/5/dicom]
and the output from the command line...
[onrc#ado2dev /]$ cd /usr2/archive/S0836VYL/6/5/dicom
-bash: cd: /usr2/archive/S0836VYL/6/5/dicom: No such file or directory
[onrc#ado2dev /]$
What am I missing??

Try to use this :
QString p("/usr2/archive/S1234ABC/5/6");
QDir d(p);
if(!d.exists() && !d.mkpath(p)) qDebug() << "Error: can't create folder '"<< p <<"'.";
else qDebug() << "Folder '"<< p <<"' exists or created successfully".
Hope it helps you.

All right, this is one for the record books...
The issue was a null terminator appended to the end of the S1234ABC string before being inserted into the database. That string was later used to create the above paths. That S1234ABC string was created using the following code:
QString prefix = "S";
QChar C1, C2, C3, etc... (randomly generated characters)
QString newID = prefix + C1 + C2 + etc...
This created a QString with a \0 on the end of it. Qt stored this value in the MySQL database, which I then pulled back into Qt and tried to make a path with it. Since it's a null terminated string, it appears normal on phpMyAdmin, in xterm, and the log files. Except... in puTTY in Windows, where I saw the weird path it was trying to create:
/usr2/archive/S0836VYL\u0000/10/3/dicom
Thankfully puTTY displayed the actual unicode value instead of ignoring it. Thank you putty! I never, ever would've figured that out...
Recreating the S1234ABC string using QStrings for each character instead of QChar fixed the issue. Now I have regular old strings in the database, and normal paths.

Related

ESP32 restarts when trying to write to NVS

I am trying to make a save system for my ESP32 project, and I have the following code:
void write_string_nvs(char *memorySlot, String key, String value)
{
nvs_handle my_handle;
esp_err_t err = nvs_open(memorySlot, NVS_READWRITE, &my_handle);
if (err == ESP_OK)
{
int kL = key.length();
int vL = value.length();
char keyA[kL + 1];
key.toCharArray(keyA, kL + 1);
char valueA[vL + 1];
value.toCharArray(valueA, vL + 1);
Serial.println("Storing \"" + String(keyA) + "\"(" + String(kL) + ")>\"" + String(valueA) + "\"(" + String(vL) + ") in NVS.");
esp_err_t err = nvs_set_blob(my_handle, keyA, &valueA, vL);
if (err == ESP_OK)
{
err = nvs_commit(my_handle);
if (err == ESP_OK)
Serial.println("Correctly saved \"" + key + "\" in " + String(memorySlot));
else
Serial.println("write_string_nvs::commit -> Could not save \"" + key + "\" in " + String(memorySlot) + ": " + esp_err_toString(err, true));
}
else
Serial.println("write_string_nvs::nvs_set_blob -> Could not save \"" + key + "\" in " + String(memorySlot) + ": " + esp_err_toString(err, true) + "");
nvs_close(my_handle);
}
else
Serial.println("Could not initialize " + String(memorySlot) + " NVS slot: " + esp_err_toString(err, true) + "");
}
I call it the following way, from a serial command:
...
String params[3];
split(serialRead, ' ', params);
String s = params[0];
String k = params[1];
String v = params[2];
bool error = false;
if (s.length() <= 0) {
error = true;
Serial.println("Please, specify an storage name");
}
if (k.length() <= 0) {
error = true;
Serial.println("Please, specify a key");
}
if (v.length() <= 0) {
error = true;
Serial.println("Please, specify a value");
}
if (!error) {
String slotName = "";
if (startsWithIgnoreCase(s, "main")) {
slotName = "storage";
}
if (startsWithIgnoreCase(s, "wifi")) {
slotName = "wifi";
}
if (slotName.length() > 1) {
Serial.println("Writing \"" + v + "\"" + " at \"\"" + k + "\" in " + slotName);
char slot[slotName.length()];
slotName.toCharArray(slot, slotName.length());
write_string_nvs(slot, k, v);
} else
Serial.println("Specified invalid slot");
}
By doing this I am trying to make a command parser to store values and read them afterwards, with the following commands: storage write <wifi/main> <key> <value> and storage read <wifi/main> <key>.
But the problem comes when I try to type the write command, and the code executes, the ESP32 Serial returns:
assertion "heap != NULL && "realloc() pointer is outside heap areas"" failed: file "/Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/heap/heap_caps.c", line 285, function: heap_caps_realloc
abort() was called at PC 0x40152903 on core 1
Backtrace: 0x40091ca4:0x3ffce0c0 0x40091ed5:0x3ffce0e0 0x40152903:0x3ffce100 0x400847a9:0x3ffce130 0x4008483d:0x3ffce150 0x4008b2e9:0x3ffce170 0x4000bedd:0x3ffce190 0x400dd4e2:0x3ffce1b0 0x400dd544:0x3ffce1d0 0x400dd6a6:0x3ffce1f0 0x400dd6d1:0x3ffce210 0x400d1b06:0x3ffce240 0x400d5939:0x3ffce260 0x400de489:0x3ffce7d0 0x40094135:0x3ffce7f0
Rebooting...
I don't know what to do, I have tried some different write and read codes, but I can't find any that stores the values correctly. The read command works, but obviously, it doesn't return anything, because the memory is empty. Here's the read command, in case you want to take a look at it:
String read_string_nvs(char *memorySlot, String key)
{
nvs_handle my_handle;
esp_err_t err = nvs_open(memorySlot, NVS_READWRITE, &my_handle);
String espErrStr = esp_err_toString(err, true);
char *value;
if (err == ESP_OK || startsWithIgnoreCase(espErrStr, "ESP_OK"))
{
size_t string_size;
int kL = key.length();
char wifi_slot[kL + 1];
key.toCharArray(wifi_slot, kL + 1);
esp_err_t err = nvs_get_str(my_handle, wifi_slot, NULL, &string_size);
value = (char *)malloc(string_size);
err = nvs_get_str(my_handle, wifi_slot, value, &string_size);
nvs_close(my_handle);
return String(value);
}
else
Serial.println("Could not open memory (\"" + espErrStr + "\")");
return espErrStr;
}
I've been with this issue for some weeks, and I really don't know what to do, maybe the system is not good for what I want, or I may be doing something wrong.
For developing I am using VSCode with PlatformIO.
Please, take a look and it and if you could tell me what's wrong or what to do, I'd be really pleased.
Thanks in advance.
I am busy with the same problem (I am going to use 4Mb of the flash as a nvs partition) and I have found a some clue: https://www.esp32.com/viewtopic.php?t=6815
It seems that problem is with the RAM-size - the system needs a RAM to create the nvs-pages-map and if it's not enough for this task - it calls the system abort.
P.S. I have decoded my firmware.elf into firmware.lst with the addresses and the assembler code and the backtrace is so:
app_main -> initArduino -> nvs_flash_init -> nvs_flash_init_partition -> nvs_flash_init_custom -> ZN3nvs_storage_init_Ejj ->ZN3nvs_Storage_populateBlobIndicesIntrusiveList -> _Znwj -> _cxa_allocate_exception -> terminatev -> cxabiv111_terminateEPFvvE - here the system aborts
To decode the .elf into .lst - just copy the firmware.elf into the folder with the xtensa-esp32-elf-objdump.exe (it is probably here .platformio\packages\toolchain-xtensa32\bin) and run in the command prompt - xtensa-esp32-elf-objdump.exe -S -l -d firmware.elf > [YourFileName].lst
These lines are problematic:
char slot[slotName.length()];
slotName.toCharArray(slot, slotName.length());
write_string_nvs(slot, k, v);
slotName.length() will return the number of characters in slotName. slot is a C string, which needs a null terminating character at the end (\0), so it needs to be declared with one byte more than the number of characters in the string. The declaration you have is too short.
You can side-step the problem by rewriting these lines as:
write_string_nvs(slotName.c_str(), k, v);
String already stores its contents as a C string internally, so the c_str() method just gives you a pointer to the buffer it manages. Be careful with this, that pointer won't be valid after the String object becomes invalid, so if the String is a variable in a function or code block, its c_str() will stop being valid when that you leave that function or code block.
Since this is some kind of heap or memory allocation issue it's possible the bug is outside of the code you shared. I would review all the code looking for instances of where you convert a String to a C character array and try using the c_str() idiom instead.
This is a pretty common problem that bites a lot of programmers.
It's also possible the problem is in your write_string_nvs() implementation.

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

CommandExecutor not registering command with multiple args?

I have a plugin that works with different commands throughout different classes. However, in this case when I have an argument length of 2, the command just doesn't seem to register.
public class FinalFrontierAdminCmds implements CommandExecutor{
FinalFrontier get;
#Override
public boolean onCommand(CommandSender sender, Command cmd, String cmdLabel,
String[] args) {
Player p = (Player) sender;
if (cmd.getName().equalsIgnoreCase("ff")){
// Check if only /ff is typed
if (args.length == 0){
p.sendMessage("This will display help menu");
return true;
}
if (args.length == 2){
if (args[0].equalsIgnoreCase("create")){
if(args[1] != null){
get.getConfig().set("Maps." + args[1] + ".world", p.getLocation().getWorld());
get.getConfig().set("Maps." + args[1] + ".x", p.getLocation().getBlockX());
get.getConfig().set("Maps." + args[1] + ".y", p.getLocation().getBlockY());
get.getConfig().set("Maps." + args[1] + ".z", p.getLocation().getBlockZ());
get.getConfig().set("Maps." + args[1] + ".isSet", false);
p.sendMessage(get.ffMsg + "You have succesfully created map " + ChatColor.GREEN + args[1] + ChatColor.YELLOW + "!");
return true;
}
}
}
return true;
}
return false;
}
}
Main class with onEnable and onDisable:
public class FinalFrontier extends JavaPlugin{
String ffMsg = ChatColor.GREEN + "[" + ChatColor.YELLOW + "Final Frontier" + ChatColor.GREEN + "]" + ChatColor.YELLOW + ": ";
public void onEnable(){
getConfig().addDefault("Maps.", "");
this.getCommand("ff").setExecutor(new FinalFrontierAdminCmds());
saveConfig();
}
public void onDisable(){
saveConfig();
}
}
This is the first time this happens to me. I usually do it like this, unless something has changed? Thanks
The line getConfig().addDefault("Maps.", ""); throws an IllegalArgumentException error because you are trying to set an empty path (the empty path being the non-existent string after the "."). If you remove the period it will create that section correctly.
I didn't see any code in your command executor class that initializes the get variable. I would add the constructor (if you haven't already done so),
public FinalFrontierAdminCmds(FinalFrontier plugin) {
this.get = plugin;
}
and update the instantiation of the object in your onEnable() method to reflect this change (this.getCommand("ff").setExecutor(new FinalFrontierAdminCmds(this))), otherwise your command executor class will throw an NPE when trying to add the values.
Last but not least I would also save the world's name (p.getLocation().getWorld().getName()) and not pass the world object itself to the set method, otherwise the config file will look something like this:
Maps:
example:
world: !!org.bukkit.craftbukkit.v1_8_R3.CraftWorld
PVP: true
ambientSpawnLimit: 15
animalSpawnLimit: 15
autoSave: true
difficulty: PEACEFUL
environment: NORMAL
fullTime: 1100
keepSpawnInMemory: true
monsterSpawnLimit: 70
thunderDuration: 32301
thundering: false
time: 1100
waterAnimalSpawnLimit: 5
weatherDuration: 56712
//more values down here
With these fixes your code should work as expected.

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.

How to effectively use if and else for a filtering construct?

To parse function parameters I get from JavaScript, I need to perform a lot of checks. For example a function might expect an object as parameter that looks like this in JavaScript.
{
Fullscreen: [ 'bool', false ],
Size: [ 'Vector2u', 800, 600 ],
Title: [ 'string', 'Hello World' ],
// more properties...
}
In C++ I parse this by looping over all keys and check them. If one of those checks fail, an error message should be printed and this key value pair should be skipped. This is how my implementation looks at the moment. I hope you doesn't get distracted from some of the engine specific calls.
ModuleSettings *module = (ModuleSettings*)HelperScript::Unwrap(args.Data());
if(args.Length() < 1 || !args[0]->IsObject())
return v8::Undefined();
v8::Handle<v8::Object> object = args[0]->ToObject();
auto stg = module->Global->Get<Settings>("settings");
v8::Handle<v8::Array> keys = object->GetPropertyNames();
for(unsigned int i = 0; i < keys->Length(); ++i)
{
string key = *v8::String::Utf8Value(keys->Get(i));
if(!object->Get(v8::String::New(key.c_str()))->IsArray())
{
HelperDebug::Fail("script", "could not parse (" + key + ") setting");
continue;
}
v8::Handle<v8::Array> values = v8::Handle<v8::Array>::Cast(object->Get(v8::String::New(key.c_str())));
if(!values->Has(0) || !values->Get(0)->IsString())
{
HelperDebug::Fail("script", "could not parse (" + key + ") setting");
continue;
}
string type = *v8::String::Utf8Value(values->Get(0));
if(type == "bool")
{
if(!values->Has(1) || !values->Get(1)->IsBoolean())
{
HelperDebug::Fail("script", "could not parse (" + key + ") setting");
continue;
}
stg->Set<bool>(key, values->Get(1)->BooleanValue());
}
else if(type == "Vector2u")
{
if(!values->Has(1) || !values->Has(2) || !values->Get(1)->IsUint32(), !values->Get(2)->IsUint32())
{
HelperDebug::Fail("script", "could not parse (" + key + ") setting");
continue;
}
stg->Set<Vector2u>(key, Vector2u(values->Get(1)->Uint32Value(), values->Get(2)->Uint32Value()));
}
else if(type == "string")
{
if(!values->Has(1) || !values->Get(1)->IsString())
{
HelperDebug::Fail("script", "could not parse (" + key + ") setting");
continue;
}
stg->Set<string>(key, *v8::String::Utf8Value(values->Get(1)));
}
}
As you can see, I defined when happens when a check fails at every filter.
HelperDebug::Fail("script", "could not parse (" + key + ") setting");
continue;
I would like to only write that once, but I can only come up with a way using goto which I would like to prevent. Is there a better option to restructure the if else construct?
I think I'd start with a set of small classes to do the verification step for each type:
auto v_string = [](v8::Handle<v8::Array> const &v) {
return v->Has(1) && v->Get(1)->IsString();
}
auto v_Vector2u = [](v8::Handle<v8::Array> const &v) {
return v->Has(1) && v->Has(2) &&
v->Get(1)->IsUint32() && v->Get(2)->IsUint32();
}
// ...
Then I'd create a map from the name of a type to the verifier for that type:
std::map<std::string, decltyp(v_string)> verifiers;
verifiers["string"] = v_string;
verifiers["Vector2u"] = v_Vector2u;
// ...
Then to verify a type, you'd use something like this:
// find the verifier for this type:
auto verifier = verifiers.find(type);
// If we can't find a verifier, reject the data:
if (verifier == verifiers.end())
HelperDebug::Fail("script", "unknown type: " + type);
// found the verifier -- verify the data:
if (!verifier->second(values))
HelperDebug::Fail("script", "could not parse (" + key + ") setting");
A pretty usual way for such situations is to use a macro. That is #defined before or in the funciton and #undef-ed at function end. As you need continue there, the other ways are pretty much hosed.
goto would also be a solution but for this particular sample I'd not go with it.
A lighter way is to put at least the fail call into a function or lambda, what still leaves you with the continue part.
I'd probably make a macro that takes the filter expression as argument, leaving code like.
if(type == "bool")
{
ENSURE(values->Has(1) && values->Get(1)->IsBoolean());
stg->Set<bool>(key, values->Get(1)->BooleanValue());
}
...