Fix updater cleanup of temporary directory on Windows

* On Windows FileOps::removeFile() fails for updater.exe
   since that file is in use by the current process.

   Whilst it is not possible to remove the file whilst it
   is in use, it can be moved or scheduled for deletion on reboot.

   This commit changes FilesOps::removeFile() to simulate the
   behavior of unlink() on Linux by moving in-use files to
   a temporary directory and then scheduling for them to
   be removed on restart.
This commit is contained in:
Robert Knight 2011-08-26 12:55:09 +01:00
parent 0134e7d53e
commit ef6809e0fe
3 changed files with 65 additions and 6 deletions

6
TODO
View file

@ -14,10 +14,8 @@ General Updater Tasks:
* Unit test working under Windows [done] * Unit test working under Windows [done]
* Start new application once installation is finished [done] * Start new application once installation is finished [done]
* Use message box for errors [done] * Use message box for errors [done]
* Newly installed binary needs to be launched un-elevated * Newly installed binary needs to be launched un-elevated [done]
* See if it is possible to overwrite application files that are in use on Windows - * See if it is possible to overwrite application files that are in use on Windows [done - it is possible]
http://stackoverflow.com/questions/2077550/how-can-i-enable-auto-updates-in-a-qt-cross-platform-app
suggests that it is possible to move the file, even if it cannot be deleted.
* Retry/cancel support if updater cannot overwrite file (eg. due to * Retry/cancel support if updater cannot overwrite file (eg. due to
file being in use by Microsoft Word or another application) file being in use by Microsoft Word or another application)

View file

@ -195,7 +195,29 @@ void FileOps::removeFile(const char* src) throw (IOException)
#else #else
if (!DeleteFile(src)) if (!DeleteFile(src))
{ {
if (GetLastError() != ERROR_FILE_NOT_FOUND) if (GetLastError() == ERROR_ACCESS_DENIED)
{
// if another process is using the file, try moving it to
// a temporary directory and then
// scheduling it for deletion on reboot
std::string tempDeletePathBase = tempPath();
tempDeletePathBase += '/';
tempDeletePathBase += fileName(src);
int suffix = 0;
std::string tempDeletePath = tempDeletePathBase;
while (fileExists(tempDeletePath.c_str()))
{
++suffix;
tempDeletePath = tempDeletePathBase + '_' + intToStr(suffix);
}
LOG(Warn,"Unable to remove file " + std::string(src) + " - it may be in use. Moving to "
+ tempDeletePath + " and scheduling delete on reboot.");
moveFile(src,tempDeletePath.c_str());
MoveFileEx(tempDeletePath.c_str(),0,MOVEFILE_DELAY_UNTIL_REBOOT);
}
else if (GetLastError() != ERROR_FILE_NOT_FOUND)
{ {
throw IOException("Unable to remove file " + std::string(src)); throw IOException("Unable to remove file " + std::string(src));
} }
@ -333,3 +355,27 @@ int FileOps::toUnixPermissions(int qtPermissions)
} }
#endif #endif
std::string FileOps::toUnixPathSeparators(const std::string& str)
{
std::string result = str;
for (int i=0; i < result.size(); i++)
{
if (result[i] == '\\')
{
result[i] = '/';
}
}
return result;
}
std::string FileOps::tempPath()
{
#ifdef PLATFORM_UNIX
return "/tmp";
#else
char buffer[MAX_PATH+1];
GetTempPath(MAX_PATH+1,buffer);
return toUnixPathSeparators(buffer);
#endif
}

View file

@ -41,10 +41,20 @@ class FileOps
ExecOther = 0x0001 ExecOther = 0x0001
}; };
/** Remove a file. Throws an exception if the file
* could not be removed.
*
* On Unix, a file can be removed even if it is in use if the user
* has the necessary permissions. removeFile() tries to simulate
* this behavior on Windows. If a file cannot be removed on Windows
* because it is in use it will be moved to a temporary directory and
* scheduled for deletion on the next restart.
*/
static void removeFile(const char* src) throw (IOException);
static void setQtPermissions(const char* path, int permissions) throw (IOException); static void setQtPermissions(const char* path, int permissions) throw (IOException);
static bool fileExists(const char* path) throw (IOException); static bool fileExists(const char* path) throw (IOException);
static void moveFile(const char* src, const char* dest) throw (IOException); static void moveFile(const char* src, const char* dest) throw (IOException);
static void removeFile(const char* src) throw (IOException);
static void extractFromZip(const char* zipFile, const char* src, const char* dest) throw (IOException); static void extractFromZip(const char* zipFile, const char* src, const char* dest) throw (IOException);
static void mkdir(const char* dir) throw (IOException); static void mkdir(const char* dir) throw (IOException);
static void rmdir(const char* dir) throw (IOException); static void rmdir(const char* dir) throw (IOException);
@ -54,8 +64,13 @@ class FileOps
static std::string dirname(const char* path); static std::string dirname(const char* path);
static void rmdirRecursive(const char* dir) throw (IOException); static void rmdirRecursive(const char* dir) throw (IOException);
static std::string canonicalPath(const char* path); static std::string canonicalPath(const char* path);
static std::string tempPath();
private: private:
static int toUnixPermissions(int qtPermissions); static int toUnixPermissions(int qtPermissions);
// returns a copy of the path 'str' with Windows-style '\'
// dir separators converted to Unix-style '/' separators
static std::string toUnixPathSeparators(const std::string& str);
}; };