131 if (!fs::exists(draftPath))
138 std::ifstream f(draftPath);
142 auto now = std::chrono::system_clock::now();
143 std::time_t now_c = std::chrono::system_clock::to_time_t(now);
144 std::stringstream ss;
145 ss << std::put_time(std::gmtime(&now_c),
"%Y-%m-%dT%H:%M:%SZ");
147 if (!j.contains(
"meta"))
148 j[
"meta"] =
nlohmann::json::object();
149 j[
"meta"][
"created"] = ss.str();
150 j[
"meta"][
"version"] = std::to_string(std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count());
152 std::ofstream out(draftPath, std::ios::trunc);
155 }
catch (
const std::exception& e) {
156 VLOG(1) <<
"Failed to inject metadata into draft: " << e.what();
160 if (fs::exists(mapFilePath)) {
161 fs::path versionDir = fs::path(mapFilePath).parent_path() /
"versions";
162 if (!fs::exists(versionDir)) {
163 fs::create_directories(versionDir);
166 auto now = std::chrono::system_clock::now();
167 auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
168 std::string filename = fs::path(mapFilePath).filename().string();
169 std::string backupPath = versionDir / (filename +
"." + std::to_string(timestamp));
171 fs::copy_file(mapFilePath, backupPath, fs::copy_options::overwrite_existing);
175 std::vector<fs::path> versions;
176 for (
const auto& entry : fs::directory_iterator(versionDir)) {
177 if (entry.path().filename().string().starts_with(filename +
".")) {
178 versions.push_back(entry.path());
181 if (versions.size() > 50) {
182 std::sort(versions.begin(), versions.end(), [](
const fs::path& a,
const fs::path& b) {
183 return fs::last_write_time(a) < fs::last_write_time(b);
185 for (size_t i = 0; i < versions.size() - 50; ++i) {
186 fs::remove(versions[i]);
193 fs::rename(draftPath, mapFilePath);
195 fs::remove(draftPath);
210 fs::path versionDir = fs::path(mapFilePath).parent_path() /
"versions";
211 std::string baseName = fs::path(mapFilePath).filename().string();
213 if (!fs::exists(versionDir))
216 for (
const auto& entry : fs::directory_iterator(versionDir)) {
217 if (entry.path().filename().string().starts_with(baseName +
".")) {
221 v
.id = entry.path().extension().string().substr(1);
228 if (j.contains(
"meta")) {
229 if (j[
"meta"].contains(
"comment"))
231 if (j[
"meta"].contains(
"created"))
232 v
.date = j[
"meta"][
"created"];
238 if (v
.date.empty()) {
240 long long ts = std::stoll(v
.id);
241 std::time_t t =
static_cast<std::time_t>(ts);
242 std::stringstream ss;
243 ss << std::put_time(std::gmtime(&t),
"%Y-%m-%dT%H:%M:%SZ");
250 history.push_back(v);
257 return std::stoll(a
.id) > std::stoll(b
.id);
268 fs::path versionDir = fs::path(mapFilePath).parent_path() /
"versions";
269 std::string baseName = fs::path(mapFilePath).filename().string();
270 fs::path backupPath = versionDir / (baseName +
"." + versionId);
272 if (!fs::exists(backupPath)) {
273 throw std::runtime_error(
"Version not found: " + versionId);
278 std::ifstream f(backupPath);
281 }
catch (
const std::exception& e) {
282 throw std::runtime_error(std::string(
"Cannot rollback: Version is invalid against current schema: ") + e.what());
286 fs::copy_file(backupPath, mapFilePath, fs::copy_options::overwrite_existing);