libslic3r utils: Add a Windows-proof file move function

This commit is contained in:
bubnikv 2018-10-10 15:47:33 +02:00
parent eb59568368
commit 55b43077d7
2 changed files with 68 additions and 0 deletions

View File

@ -44,6 +44,11 @@ extern local_encoded_string encode_path(const char *src);
extern std::string decode_path(const char *src); extern std::string decode_path(const char *src);
extern std::string normalize_utf8_nfc(const char *src); extern std::string normalize_utf8_nfc(const char *src);
// Safely rename a file even if the target exists.
// On Windows, the file explorer (or anti-virus or whatever else) often locks the file
// for a short while, so the file may not be movable. Retry while we see recoverable errors.
extern int rename_file(const std::string &from, const std::string &to);
// File path / name / extension splitting utilities, working with UTF-8, // File path / name / extension splitting utilities, working with UTF-8,
// to be published to Perl. // to be published to Perl.
namespace PerlUtils { namespace PerlUtils {

View File

@ -23,6 +23,7 @@
#include <boost/nowide/fstream.hpp> #include <boost/nowide/fstream.hpp>
#include <boost/nowide/integration/filesystem.hpp> #include <boost/nowide/integration/filesystem.hpp>
#include <boost/nowide/convert.hpp> #include <boost/nowide/convert.hpp>
#include <boost/nowide/cstdio.hpp>
#include <tbb/task_scheduler_init.h> #include <tbb/task_scheduler_init.h>
@ -149,6 +150,68 @@ const std::string& data_dir()
return g_data_dir; return g_data_dir;
} }
// borrowed from LLVM lib/Support/Windows/Path.inc
int rename_file(const std::string &from, const std::string &to)
{
int ec = 0;
#ifdef _WIN32
// Convert to utf-16.
std::wstring wide_from = boost::nowide::widen(from);
std::wstring wide_to = boost::nowide::widen(to);
// Retry while we see recoverable errors.
// System scanners (eg. indexer) might open the source file when it is written
// and closed.
bool TryReplace = true;
// This loop may take more than 2000 x 1ms to finish.
for (int i = 0; i < 2000; ++ i) {
if (i > 0)
// Sleep 1ms
::Sleep(1);
if (TryReplace) {
// Try ReplaceFile first, as it is able to associate a new data stream
// with the destination even if the destination file is currently open.
if (::ReplaceFileW(wide_to.data(), wide_from.data(), NULL, 0, NULL, NULL))
return 0;
DWORD ReplaceError = ::GetLastError();
ec = -1; // ReplaceError
// If ReplaceFileW returned ERROR_UNABLE_TO_MOVE_REPLACEMENT or
// ERROR_UNABLE_TO_MOVE_REPLACEMENT_2, retry but only use MoveFileExW().
if (ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT ||
ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) {
TryReplace = false;
continue;
}
// If ReplaceFileW returned ERROR_UNABLE_TO_REMOVE_REPLACED, retry
// using ReplaceFileW().
if (ReplaceError == ERROR_UNABLE_TO_REMOVE_REPLACED)
continue;
// We get ERROR_FILE_NOT_FOUND if the destination file is missing.
// MoveFileEx can handle this case.
if (ReplaceError != ERROR_ACCESS_DENIED && ReplaceError != ERROR_FILE_NOT_FOUND && ReplaceError != ERROR_SHARING_VIOLATION)
break;
}
if (::MoveFileExW(wide_from.c_str(), wide_to.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING))
return 0;
DWORD MoveError = ::GetLastError();
ec = -1; // MoveError
if (MoveError != ERROR_ACCESS_DENIED && MoveError != ERROR_SHARING_VIOLATION)
break;
}
#else
boost::nowide::remove(to.c_str());
ec = boost::nowide::rename(from.c_str(), to.c_str());
#endif
return ec;
}
} // namespace Slic3r } // namespace Slic3r
#include <xsinit.h> #include <xsinit.h>