Unravel Engine C++ Reference
Loading...
Searching...
No Matches
asset_writer.cpp
Go to the documentation of this file.
1#include "asset_writer.h"
2
3#ifdef _WIN32
4#include <windows.h>
5#else
6#include <fcntl.h> // open
7#include <unistd.h> // fsync, close
8#endif
9
10#include <chrono>
11#include <random>
12#include <thread>
13
14
15namespace unravel
16{
17namespace asset_writer
18{
19
20namespace
21{
22constexpr const char charset[] = "0123456789"
23 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
24 "abcdefghijklmnopqrstuvwxyz";
25
26constexpr size_t max_index = (sizeof(charset) - 1);
27
28using random_generator_t = ::std::mt19937;
29
30static const auto make_seeded_engine = []()
31{
32 std::random_device r;
33 std::hash<std::thread::id> hasher;
34 std::seed_seq seed(std::initializer_list<typename random_generator_t::result_type>{
35 static_cast<typename random_generator_t::result_type>(
36 std::chrono::system_clock::now().time_since_epoch().count()),
37 static_cast<typename random_generator_t::result_type>(hasher(std::this_thread::get_id())),
38 r(),
39 r(),
40 r(),
41 r(),
42 r(),
43 r(),
44 r(),
45 r()});
46 return random_generator_t(seed);
47};
48
49std::string generate_random_string(size_t len)
50{
51 static thread_local random_generator_t engine(make_seeded_engine());
52
53 std::uniform_int_distribution<> dist(0, max_index);
54
55 std::string str;
56 str.reserve(len);
57
58 for (size_t i = 0; i < len; i++)
59 {
60 str.push_back(charset[dist(engine)]);
61 }
62
63 return str;
64}
65}
66#define ATOMIC_SAVE
67auto sync_file(const fs::path& temp, fs::error_code& ec) noexcept -> bool
68{
69#ifdef _WIN32
70 // flush via FlushFileBuffers
71 {
72 HANDLE h = CreateFileW(temp.wstring().c_str(),
73 GENERIC_WRITE,
74 FILE_SHARE_READ | FILE_SHARE_WRITE,
75 nullptr,
76 OPEN_EXISTING,
77 FILE_ATTRIBUTE_NORMAL,
78 nullptr);
79 if(h == INVALID_HANDLE_VALUE)
80 {
81 ec = fs::error_code(GetLastError(), std::system_category());
82 fs::remove(temp, ec);
83 return false;
84 }
85 if(!FlushFileBuffers(h))
86 {
87 ec = fs::error_code(GetLastError(), std::system_category());
88 CloseHandle(h);
89 fs::remove(temp, ec);
90 return false;
91 }
92 CloseHandle(h);
93 }
94#else
95
96 int fd = ::open(temp.c_str(), O_RDWR);
97 if(fd < 0)
98 {
99 ec = fs::error_code(errno, std::generic_category());
100 fs::remove(temp, ec);
101 return false;
102 }
103 if(::fsync(fd) < 0)
104 {
105 ec = fs::error_code(errno, std::generic_category());
106 ::close(fd);
107 fs::remove(temp, ec);
108 return false;
109 }
110 ::close(fd);
111#endif
112
113 return true;
114}
115
116//------------------------------------------------------------------------------
117// Atomically rename src -> dst, overwriting dst if it exists.
118//------------------------------------------------------------------------------
119auto atomic_rename_file(const fs::path& src, const fs::path& dst, fs::error_code& ec) noexcept -> bool
120{
121 ec.clear();
122 fs::rename(src, dst, ec);
123 return !ec;
124}
125
126//------------------------------------------------------------------------------
127// Generate a unique temp‑path in `dir` using `base`.
128// Retries up to 100 times before giving up.
129//------------------------------------------------------------------------------
130auto make_temp_path(const fs::path& dir, fs::path& out, fs::error_code& ec) noexcept -> bool
131{
132 ec.clear();
133 if(!fs::exists(dir, ec) || ec)
134 return false;
135 if(!fs::is_directory(dir, ec) || ec)
136 return false;
137
138 out = dir / ("." + hpp::to_string(generate_uuid()) + ".temp");
139 // while(true)
140 // {
141 // out = dir / ("." + generate_random_string(16) + ".temp");
142 // if(!fs::exists(out, ec) || ec)
143 // break;
144 // }
145 out.make_preferred();
146
147 return true;
148}
149
150//------------------------------------------------------------------------------
151// Atomically copy src -> dst via:
152// 1) copy_file(src, temp)
153// 2) flush temp to disk
154// 3) atomic rename(temp, dst)
155//------------------------------------------------------------------------------
156auto atomic_copy_file(const fs::path& src, const fs::path& dst, fs::error_code& ec) noexcept -> bool
157{
158 ec.clear();
159
160 // 1) validate src
161 if(!fs::exists(src, ec) || ec)
162 {
163 if(!ec)
164 ec = std::make_error_code(std::errc::no_such_file_or_directory);
165 return false;
166 }
167 if(!fs::is_regular_file(src, ec) || ec)
168 {
169 if(!ec)
170 ec = std::make_error_code(std::errc::invalid_argument);
171 return false;
172 }
173
174 // 3) generate a unique temp filename
175 fs::path temp;
176 if(!make_temp_path(dst.parent_path(), temp, ec))
177 return false;
178
179 fs::copy_file(src, temp, fs::copy_options::overwrite_existing, ec);
180 if(ec)
181 {
182 fs::remove(temp, ec);
183 return false;
184 }
185
186 if(!sync_file(temp, ec))
187 {
188 fs::remove(temp, ec);
189 return false;
190 }
191
192 // 5) finally swap it in place
193 if(!atomic_rename_file(temp, dst, ec))
194 {
195 fs::remove(temp, ec);
196 return false;
197 }
198
199 return true;
200}
201
202void atomic_write_file(const fs::path& dst,
203 const std::function<void(const fs::path&)>& callback,
204 fs::error_code& ec) noexcept
205{
206 fs::error_code err;
207#ifdef ATOMIC_SAVE
208 fs::path temp;
209 make_temp_path(dst.parent_path(), temp, err);
210#else
211 fs::path temp = fs::temp_directory_path(err);
212 temp /= "." + hpp::to_string(generate_uuid()) + ".temp";
213#endif
214
215 callback(temp);
216
217#ifdef ATOMIC_SAVE
218 sync_file(temp, err);
219 atomic_rename_file(temp, dst, err);
220#else
221 fs::copy_file(temp, dst, err);
222#endif
223 fs::remove(temp, err);
224
225}
226} // namespace asset_writer
227} // namespace unravel
auto make_temp_path(const fs::path &dir, fs::path &out, fs::error_code &ec) noexcept -> bool
auto atomic_rename_file(const fs::path &src, const fs::path &dst, fs::error_code &ec) noexcept -> bool
auto atomic_copy_file(const fs::path &src, const fs::path &dst, fs::error_code &ec) noexcept -> bool
auto sync_file(const fs::path &temp, fs::error_code &ec) noexcept -> bool
void atomic_write_file(const fs::path &dst, const std::function< void(const fs::path &)> &callback, fs::error_code &ec) noexcept
auto generate_uuid() -> hpp::uuid
Definition uuid.cpp:25