Make WriteFile replace rather than overwrite

This preserves old file if writing the new one fails for some reason.
This commit is contained in:
Tamás Bálint Misius 2023-01-19 16:51:23 +01:00
parent 163203b321
commit a7d8ecc6e3
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
3 changed files with 41 additions and 8 deletions

View File

@ -1,5 +1,6 @@
#include "Platform.h" #include "Platform.h"
#include "resource.h" #include "resource.h"
#include "tpt-rand.h"
#include <memory> #include <memory>
#include <cstring> #include <cstring>
#include <fstream> #include <fstream>
@ -186,9 +187,14 @@ bool RemoveFile(ByteString filename)
#endif #endif
} }
bool RenameFile(ByteString filename, ByteString newFilename) bool RenameFile(ByteString filename, ByteString newFilename, bool replace)
{ {
#ifdef WIN #ifdef WIN
if (replace)
{
// TODO: we rely on errno but errors from this are available through GetLastError(); fix
return MoveFileExW(WinWiden(filename).c_str(), WinWiden(newFilename).c_str(), MOVEFILE_REPLACE_EXISTING);
}
return _wrename(WinWiden(filename).c_str(), WinWiden(newFilename).c_str()) == 0; return _wrename(WinWiden(filename).c_str(), WinWiden(newFilename).c_str()) == 0;
#else #else
return rename(filename.c_str(), newFilename.c_str()) == 0; return rename(filename.c_str(), newFilename.c_str()) == 0;
@ -335,16 +341,43 @@ bool ReadFile(std::vector<char> &fileData, ByteString filename)
return true; return true;
} }
bool WriteFile(std::vector<char> fileData, ByteString filename, bool replaceAtomically) bool WriteFile(const std::vector<char> &fileData, ByteString filename)
{ {
// TODO: replaceAtomically auto replace = FileExists(filename);
std::ofstream f(filename, std::ios::binary); auto writeFileName = filename;
if (f) f.write(&fileData[0], fileData.size()); if (replace)
{
while (true)
{
writeFileName = ByteString::Build(filename, ".temp.", random_gen() % 100000);
if (!FileExists(writeFileName))
{
break;
}
}
}
std::ofstream f(writeFileName, std::ios::binary);
if (f)
{
f.write(&fileData[0], fileData.size());
}
if (!f) if (!f)
{ {
std::cerr << "WriteFile: " << filename << ": " << strerror(errno) << std::endl; std::cerr << "WriteFile: " << filename << ": " << strerror(errno) << std::endl;
if (replace)
{
RemoveFile(writeFileName);
}
return false; return false;
} }
if (replace)
{
if (!RenameFile(writeFileName, filename, true))
{
RemoveFile(writeFileName);
return false;
}
}
return true; return true;
} }

View File

@ -21,7 +21,7 @@ namespace Platform
* @return true on success * @return true on success
*/ */
bool RemoveFile(ByteString filename); bool RemoveFile(ByteString filename);
bool RenameFile(ByteString filename, ByteString newFilename); bool RenameFile(ByteString filename, ByteString newFilename, bool replace = false);
/** /**
* @return true on success * @return true on success
@ -35,7 +35,7 @@ namespace Platform
std::vector<ByteString> DirectorySearch(ByteString directory, ByteString search, std::vector<ByteString> extensions); std::vector<ByteString> DirectorySearch(ByteString directory, ByteString search, std::vector<ByteString> extensions);
bool ReadFile(std::vector<char> &fileData, ByteString filename); bool ReadFile(std::vector<char> &fileData, ByteString filename);
bool WriteFile(std::vector<char> fileData, ByteString filename, bool replaceAtomically = false); // TODO: Revisit call sites, remove default. bool WriteFile(const std::vector<char> &fileData, ByteString filename);
ByteString WinNarrow(const std::wstring &source); ByteString WinNarrow(const std::wstring &source);
std::wstring WinWiden(const ByteString &source); std::wstring WinWiden(const ByteString &source);

View File

@ -47,7 +47,7 @@ void Prefs::Write()
Json::StreamWriterBuilder wbuilder; Json::StreamWriterBuilder wbuilder;
wbuilder["indentation"] = "\t"; wbuilder["indentation"] = "\t";
ByteString data = Json::writeString(wbuilder, root); ByteString data = Json::writeString(wbuilder, root);
if (!Platform::WriteFile(std::vector<char>(data.begin(), data.end()), path, true)) if (!Platform::WriteFile(std::vector<char>(data.begin(), data.end()), path))
{ {
return; return;
} }