4#include "Stroika/Foundation/StroikaPreComp.h"
10#if qStroika_Foundation_Common_Platform_Windows
12#elif qStroika_Foundation_Common_Platform_POSIX
18#include "Stroika/Foundation/Containers/Common.h"
19#include "Stroika/Foundation/Execution/Activity.h"
20#include "Stroika/Foundation/Execution/Exceptions.h"
22#include "Stroika/Foundation/Execution/Throw.h"
23#if qStroika_Foundation_Common_Platform_Windows
24#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
35using namespace Stroika::Foundation::IO;
38#if qStroika_Foundation_Common_Platform_Windows
44 bool tryCreateFile_ (
const filesystem::path& p)
48 if (filesystem::exists (p, ec)) {
53 DbgTrace (
"Error checking file existence: {}"_f, String::FromNarrowSDKString (ec.message ()));
75ThroughTmpFileWriter::ThroughTmpFileWriter (
const filesystem::path& realFileName,
const String& tmpSuffix)
76 : fRealFilePath_{realFileName}
78 Require (not realFileName.empty ());
79 Require (not tmpSuffix.empty ());
81 filesystem::path useTmpPath = realFileName;
82 useTmpPath.replace_extension ();
83 String baseStem{useTmpPath.stem ()};
84 filesystem::path newExtension = tmpSuffix.
As<filesystem::path> ();
85 create_directories (useTmpPath.parent_path ());
86 default_random_engine gen{random_device{}()};
87 uniform_int_distribution<int> distribution{1, 99999};
89 filesystem::path newFN =
"{}-{}"_f(baseStem, distribution (gen)).As<filesystem::path> ();
90 useTmpPath.replace_filename (newFN);
91 useTmpPath.replace_extension (newExtension);
92 if (tryCreateFile_ (useTmpPath)) {
93 fTmpFilePath_ = useTmpPath;
96 DbgTrace (
"randomfile name conflict, so trying again (should be rare): f={}"_f, useTmpPath);
101ThroughTmpFileWriter::~ThroughTmpFileWriter ()
103 if (not fTmpFilePath_.empty ()) {
104 DbgTrace (
"ThroughTmpFileWriter::DTOR - tmpfile not successfully commited to {}"_f, fRealFilePath_);
106#if qStroika_Foundation_Common_Platform_POSIX
107 (void)::unlink (fTmpFilePath_.c_str ());
108#elif qStroika_Foundation_Common_Platform_Windows
110 if (::DeleteFileW (fTmpFilePath_.c_str ()) == 0) {
111 (void)::MoveFileExW (fTmpFilePath_.c_str (),
nullptr, MOVEFILE_DELAY_UNTIL_REBOOT);
121 Require (not fTmpFilePath_.empty ());
124 auto activity =
LazyEvalActivity ([&] () ->
String {
return "committing temporary file '{}' to '{}'"_f(fTmpFilePath_, fRealFilePath_); });
126#if qStroika_Foundation_Common_Platform_POSIX
128#elif qStroika_Foundation_Common_Platform_Windows
130 ThrowIfZeroGetLastError (::MoveFileExW (fTmpFilePath_.c_str (), fRealFilePath_.c_str (), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH));
132 catch (
const system_error& we) {
134 if (we.code () == error_code{ERROR_CALL_NOT_IMPLEMENTED, system_category ()}) {
135 ::DeleteFileW (fRealFilePath_.c_str ());
136 ThrowIfZeroGetLastError (::MoveFileW (fTmpFilePath_.c_str (), fRealFilePath_.c_str ()));
140 else if (we.code () == error_code{ERROR_SHARING_VIOLATION, system_category ()} or
141 we.code () == error_code{ERROR_ACCESS_DENIED, system_category ()}) {
142 auto retryLoop = [&] () {
143 if (fRetryOnSharingViolationFor != kRetryOnSharingViolationFor_Disable) {
144 DbgTrace (
"ThroughTmpFileWriter::Commit: {}, so retrying for {}"_f,
145 we.code ().value () == ERROR_SHARING_VIOLATION ?
"ERROR_SHARING_VIOLATION"_k :
"ERROR_ACCESS_DENIED"_k,
146 fRetryOnSharingViolationFor.value_or (kRetryOnSharingViolationFor_Default));
147 Time::TimePointSeconds until = Time::GetTickCount () + fRetryOnSharingViolationFor.value_or (kRetryOnSharingViolationFor_Default);
148 unsigned int nRetries = 0;
151 if (BOOL r = ::MoveFileExW (fTmpFilePath_.c_str (), fRealFilePath_.c_str (), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
156 DWORD lastError = ::GetLastError ();
157 Assert (lastError != 0);
158 if (lastError != ERROR_SHARING_VIOLATION) {
159 Execution::ThrowSystemErrNo (lastError);
163 }
while (until < Time::GetTickCount ());
176 fTmpFilePath_.clear ();
#define WeakAssertNotReached()
#define AssertNotImplemented()
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
static INT_TYPE ThrowPOSIXErrNoIfNegative(INT_TYPE returnCode, const path &p1={}, const path &p2={})
void Sleep(Time::Duration seconds2Wait)