How to properly call QFile::remove() on Windows?
-
I am trying to debug my application in Qt Creator using SQLite3 database files which the user can create, open, and overwrite when a new database is created... At least on Linux, I can always overwrite the old database if it is owned by the current user.
On Windows, I am getting an error stating that the process cannot access the file because it is in use by another process. If I look at the file in Windows Explorer at the moment that happens, it says that it is in use by my application. So I inserted this code in the hopes that it would solve the error:
QFile x(absFilePath); #ifdef Q_OS_WINDOWS if (x.isOpen()) { x.close(); } #endif if (!x.remove()) { const QString title = tr("Could Not Remove Existing File"); const QString msg = tr("The file:\n\n%1\n\ncould not be removed.\n\nError message:\n%2"); QMessageBox::warning(this, title, msg.arg(absFilePath).arg(x.errorString())); return; }However, this doesn't let me remove the file. The file path in
absFilePathis valid, the file exists, and permissions are adequate to allow deletion of the file. This is all checked in code that runs just before this extract is executed. And any objects such as QFile or QFileInfo which might refer to this file are put in their own scope so that when the above bit runs, they all go out of scope.I can call the static version of
QFile::remove(absFilePath)which DOES seem to work, but then I won't be able to get any error messages from QFileDevice when it doesn't work. Is there a better way to do this but still get an error message when it doesn't work? -
I am trying to debug my application in Qt Creator using SQLite3 database files which the user can create, open, and overwrite when a new database is created... At least on Linux, I can always overwrite the old database if it is owned by the current user.
On Windows, I am getting an error stating that the process cannot access the file because it is in use by another process. If I look at the file in Windows Explorer at the moment that happens, it says that it is in use by my application. So I inserted this code in the hopes that it would solve the error:
QFile x(absFilePath); #ifdef Q_OS_WINDOWS if (x.isOpen()) { x.close(); } #endif if (!x.remove()) { const QString title = tr("Could Not Remove Existing File"); const QString msg = tr("The file:\n\n%1\n\ncould not be removed.\n\nError message:\n%2"); QMessageBox::warning(this, title, msg.arg(absFilePath).arg(x.errorString())); return; }However, this doesn't let me remove the file. The file path in
absFilePathis valid, the file exists, and permissions are adequate to allow deletion of the file. This is all checked in code that runs just before this extract is executed. And any objects such as QFile or QFileInfo which might refer to this file are put in their own scope so that when the above bit runs, they all go out of scope.I can call the static version of
QFile::remove(absFilePath)which DOES seem to work, but then I won't be able to get any error messages from QFileDevice when it doesn't work. Is there a better way to do this but still get an error message when it doesn't work?@Robert-Hairgrove said in How to properly call QFile::remove() on Windows?:
On Windows, I am getting an error stating that the process cannot access the file because it is in use by another process.
It is not clear when it happens:
a) When SQLite tries to write to that file
b) When you're trying to delete itIf it is b) - is the file still in use by SQLite?
-
@Robert-Hairgrove said in How to properly call QFile::remove() on Windows?:
On Windows, I am getting an error stating that the process cannot access the file because it is in use by another process.
It is not clear when it happens:
a) When SQLite tries to write to that file
b) When you're trying to delete itIf it is b) - is the file still in use by SQLite?
@jsulm Nothing is writing to the file, otherwise the static
QFile::remove()would also fail (but it doesn't). -
The static
QFile::removeis literally defined asreturn QFile(fileName).remove(). And the non-static version closes the file by itself if it is open.There is no difference in mechanism, so any difference in behavior you observe may be a coincidence. There is not enough information in the post to determine. I'd check that the SQLite connection is completely closed and discarded before removal of the file; note that even on Linux where it is possible to unlink files with open descriptors doing so to SQLite databases is never safe - the WAL or rollback journal file may lose association which can lead to database corruption,
As for getting the error from the static version, you can call the Win32 API method
GetLastErroryourself, if you'd like. -
May or may not be relevant but in general terms when you create a DB object handle to a database the DB file becomes (open) for the duration of the DB handle. Make sure you are destroying the DB handle before attempting to remove the database file.
Yeah, what IgKh said above...
-
The static
QFile::removeis literally defined asreturn QFile(fileName).remove(). And the non-static version closes the file by itself if it is open.There is no difference in mechanism, so any difference in behavior you observe may be a coincidence. There is not enough information in the post to determine. I'd check that the SQLite connection is completely closed and discarded before removal of the file; note that even on Linux where it is possible to unlink files with open descriptors doing so to SQLite databases is never safe - the WAL or rollback journal file may lose association which can lead to database corruption,
As for getting the error from the static version, you can call the Win32 API method
GetLastErroryourself, if you'd like.@IgKh and @Kent-Dorfman : Thank you, this is very helpful information. My application does call
QSqlDatabase::removeDatabase()on the connection before it tries to delete the file, but maybe I need to also callQApplication::processEvents()?The tip about calling
GetLastError()is a good one in situations like this. I will test this again before marking it solved. -
@IgKh and @Kent-Dorfman : Thank you, this is very helpful information. My application does call
QSqlDatabase::removeDatabase()on the connection before it tries to delete the file, but maybe I need to also callQApplication::processEvents()?The tip about calling
GetLastError()is a good one in situations like this. I will test this again before marking it solved.@Robert-Hairgrove
If you say you can literally putQFile::remove(x)where you havex.remove()and it works then I would look at/show what you exactly have done with yourQFile.Also, assuming you are using
QSqlDatabase, have you done what you're not supposed to do and kept a member variable copy of it? -
@Andy314
Although I am sure this in itself is a useful recommendation, and may well solve the problem for whatever reason, did you really have the same problem whereQFile file(...); file.remove();failed whileQFile::remove(...);succeeded? If so, do you have a repro? This is what is strange here. Because it seems like that would only be the case depending on what you had done with yourQFile fileinstance.... -
@Andy314
Although I am sure this in itself is a useful recommendation, and may well solve the problem for whatever reason, did you really have the same problem whereQFile file(...); file.remove();failed whileQFile::remove(...);succeeded? If so, do you have a repro? This is what is strange here. Because it seems like that would only be the case depending on what you had done with yourQFile fileinstance....@JonB and @Andy314 : Thanks for the feedback. I am pretty sure that the database connection is being handled properly. It is possible that it has to do with my environment ... running in Oracle VirtualBox in a Windows 10 guest on a Linux host. I had an issue once where I could not remove a USB stick in the virtual environment for several minutes because Windows thought it was being used, where it certainly wasn't. I don't have any anti-virus software running in the guest, but maybe it was just Windows doing its housekeeping?
I need to look at the Qt source code some more ... if all else fails, I can try putting the call to
QFile::remove()in awhileloop and callstd::yieldorQApplication::processEvents(). -
@JonB and @Andy314 : Thanks for the feedback. I am pretty sure that the database connection is being handled properly. It is possible that it has to do with my environment ... running in Oracle VirtualBox in a Windows 10 guest on a Linux host. I had an issue once where I could not remove a USB stick in the virtual environment for several minutes because Windows thought it was being used, where it certainly wasn't. I don't have any anti-virus software running in the guest, but maybe it was just Windows doing its housekeeping?
I need to look at the Qt source code some more ... if all else fails, I can try putting the call to
QFile::remove()in awhileloop and callstd::yieldorQApplication::processEvents().@Robert-Hairgrove
In your sample code you just show:QFile x(absFilePath); #ifdef Q_OS_WINDOWS if (x.isOpen()) { x.close(); } #endif if (!x.remove()) {- Put in a debug, do you actually hit that
x.close()? - Please show what you actually do with the
QFile xin between creating the variable and the close for the attempted remove.
Oh, and to be 100% clear, the
absFilePathis the path to the actual database file on disk opened byQSqlDatabase, right? - Put in a debug, do you actually hit that
-
@Robert-Hairgrove
In your sample code you just show:QFile x(absFilePath); #ifdef Q_OS_WINDOWS if (x.isOpen()) { x.close(); } #endif if (!x.remove()) {- Put in a debug, do you actually hit that
x.close()? - Please show what you actually do with the
QFile xin between creating the variable and the close for the attempted remove.
Oh, and to be 100% clear, the
absFilePathis the path to the actual database file on disk opened byQSqlDatabase, right?@JonB said in How to properly call QFile::remove() on Windows?:
@Robert-Hairgrove
to be 100% clear, theabsFilePathis the path to the actual database file on disk opened byQSqlDatabase, right?No ...
absFilePathis the name of the file chosen by the user which will be the name of the new database. It just so happens that this is an existing file in this part of the code, which happens to be a database. There is no open database connection when this code runs, and there is nothing between theQFile x(...)statement and the#ifdef.... The application ensures that any open database is closed (i.e. the connection is removed) before letting the user create a new one.Like @IgKh said, it is pretty much what is in the Qt source code for
QFile::remove()... it also closes the file before trying to remove it. And if it isn't open, then I assume there is no need to callclose(). But maybe I should not trustQFile::isOpen()and just callclose()in any case? - Put in a debug, do you actually hit that
-
Maybe this is overkill for something which should be very simple, but at least it is working.
The code right before the following performs some checks on exisence and file permissions. If the user chooses to delete the file, then this part runs:QString errstring; if (delete_existing_file) { std::chrono::microseconds us(0); const std::chrono::microseconds us_begin(0); const std::chrono::microseconds interval(500); const std::chrono::microseconds max_wait(1000000); { // start a new scope... QFile x(absFilePath); x.close(); // The following snippet taken from the example for std::this_thread::yield(): // https://en.cppreference.com/w/cpp/thread/yield.html auto wait_for_remove = [](std::chrono::microseconds s) { auto begin = std::chrono::high_resolution_clock::now(); auto end = begin + s; do { std::this_thread::yield(); } while(std::chrono::high_resolution_clock::now() < end); }; while (!x.remove()) { us += interval; wait_for_remove(interval); if (us >= max_wait) break; } errstring = x.errorString(); } // x goes out of scope here... if (us > us_begin) { if (us < max_wait) { // Success, but we waited at least once: qDebug() << "Waited for QFile::remove() in microseconds: " << us; } else { title = tr("Problem Removing Existing File"); msg = tr("The file '%1' could not be removed.\n\nError message:\n%2") .arg(absFilePath).arg(errstring); QMessageBox::warning(this,title,msg); qDebug() << "Failed to remove file: " << absFilePath << "Error: " << errstring; return; } } } -
R Robert Hairgrove has marked this topic as solved
-
@JonB said in How to properly call QFile::remove() on Windows?:
@Robert-Hairgrove
to be 100% clear, theabsFilePathis the path to the actual database file on disk opened byQSqlDatabase, right?No ...
absFilePathis the name of the file chosen by the user which will be the name of the new database. It just so happens that this is an existing file in this part of the code, which happens to be a database. There is no open database connection when this code runs, and there is nothing between theQFile x(...)statement and the#ifdef.... The application ensures that any open database is closed (i.e. the connection is removed) before letting the user create a new one.Like @IgKh said, it is pretty much what is in the Qt source code for
QFile::remove()... it also closes the file before trying to remove it. And if it isn't open, then I assume there is no need to callclose(). But maybe I should not trustQFile::isOpen()and just callclose()in any case?@Robert-Hairgrove said in How to properly call QFile::remove() on Windows?:
there is nothing between the QFile x(...) statement and the #ifdef....
QFile x(absFilePath); x.close();Then I have no idea why you are creating any
QFileinstance here at all. And if for unknown reason it is causing problem just remove it, why have you put this in? Oh, did you say purely in order to be able to gox.remove()so that you can getQFile::error()information for message? Certainly you should not need theclose(), and I asked earlier if yourif (x.isOpen())was ever true, because with this code it should never be?Looks like we shall never know why this fails when the static call succeeds....
-
@Robert-Hairgrove said in How to properly call QFile::remove() on Windows?:
there is nothing between the QFile x(...) statement and the #ifdef....
QFile x(absFilePath); x.close();Then I have no idea why you are creating any
QFileinstance here at all. And if for unknown reason it is causing problem just remove it, why have you put this in? Oh, did you say purely in order to be able to gox.remove()so that you can getQFile::error()information for message? Certainly you should not need theclose(), and I asked earlier if yourif (x.isOpen())was ever true, because with this code it should never be?Looks like we shall never know why this fails when the static call succeeds....
@JonB Here you can read the Qt6 source code of
QFile::remove()... the first link is to the non-static member function, and the second is to the static member function. My code does pretty much the same thing except for the"while {...}"loop I have written:https://codebrowser.dev/qt6/qtbase/src/corelib/io/qfile.cpp.html#421
https://codebrowser.dev/qt6/qtbase/src/corelib/io/qfile.cpp.html#452Does this help?
And since there is basically no difference in the actual code, as I said ... I do want to get the error message.
-
Maybe this is overkill for something which should be very simple, but at least it is working.
The code right before the following performs some checks on exisence and file permissions. If the user chooses to delete the file, then this part runs:QString errstring; if (delete_existing_file) { std::chrono::microseconds us(0); const std::chrono::microseconds us_begin(0); const std::chrono::microseconds interval(500); const std::chrono::microseconds max_wait(1000000); { // start a new scope... QFile x(absFilePath); x.close(); // The following snippet taken from the example for std::this_thread::yield(): // https://en.cppreference.com/w/cpp/thread/yield.html auto wait_for_remove = [](std::chrono::microseconds s) { auto begin = std::chrono::high_resolution_clock::now(); auto end = begin + s; do { std::this_thread::yield(); } while(std::chrono::high_resolution_clock::now() < end); }; while (!x.remove()) { us += interval; wait_for_remove(interval); if (us >= max_wait) break; } errstring = x.errorString(); } // x goes out of scope here... if (us > us_begin) { if (us < max_wait) { // Success, but we waited at least once: qDebug() << "Waited for QFile::remove() in microseconds: " << us; } else { title = tr("Problem Removing Existing File"); msg = tr("The file '%1' could not be removed.\n\nError message:\n%2") .arg(absFilePath).arg(errstring); QMessageBox::warning(this,title,msg); qDebug() << "Failed to remove file: " << absFilePath << "Error: " << errstring; return; } } }@Robert-Hairgrove said in How to properly call QFile::remove() on Windows?:
Small correction:if (us < max_wait) {
// Success, but we waited at least once:
qDebug() << "Waited for QFile::remove() in microseconds: " << us.count(); // <== need to use ".count()" here...
} else {
// etc.