8#include "../../libs/json.hpp"
17namespace fs = std::filesystem;
29#define mkdir(path, mode) _mkdir(path)
31#define S_ISDIR(mode) (((mode) & _S_IFMT) == _S_IFDIR)
34#define S_ISREG(mode) (((mode) & _S_IFMT) == _S_IFREG)
41using json = nlohmann::json;
50 const std::string& snapshotsDir) {
59 std::string snapshotPath = snapshotsDir +
"/" + snapshotName;
62 if (DirectoryExists(snapshotPath)) {
64 auto now = std::time(
nullptr);
65 std::ostringstream oss;
66 oss << snapshotPath <<
"_" << now;
67 snapshotPath = oss.str();
71 if (!CopyDirectory(studyPath, snapshotPath,
true)) {
83 if (!DirectoryExists(snapshotPath)) {
85 result.
errors.push_back(
"Snapshot directory does not exist");
90 std::string studyInfoPath = snapshotPath +
"/study-info.json";
91 if (!FileExists(studyInfoPath)) {
93 result.
errors.push_back(
"study-info.json not found");
101 result.
errors.push_back(
"Failed to parse study-info.json");
106 auto studyValidation = study->Validate();
107 result.
errors.insert(result.
errors.end(), studyValidation.errors.begin(), studyValidation.errors.end());
108 result.
warnings.insert(result.
warnings.end(), studyValidation.warnings.begin(), studyValidation.warnings.end());
111 std::string chainsDir = snapshotPath +
"/chains";
112 if (!DirectoryExists(chainsDir)) {
113 result.
warnings.push_back(
"chains/ directory not found");
117 std::string testsDir = snapshotPath +
"/tests";
118 if (!DirectoryExists(testsDir)) {
119 result.
warnings.push_back(
"tests/ directory not found");
123 std::string dataDir = snapshotPath +
"/data";
124 if (DirectoryExists(dataDir)) {
125 result.
warnings.push_back(
"Snapshot contains data/ directory (should be excluded)");
129 for (
const auto& test : study->GetTests()) {
130 std::string testDataDir = snapshotPath +
"/tests/" + test.testPath +
"/data";
131 if (DirectoryExists(testDataDir)) {
132 result.
warnings.push_back(
"Test " + test.testName +
" contains data/ directory");
141 const std::string& studiesDir,
142 const std::string& newStudyName) {
143 std::string studyPath = studiesDir +
"/" + newStudyName;
146 if (DirectoryExists(studyPath)) {
153 if (!CopyDirectory(snapshotPath, studyPath,
false)) {
158 std::string dataDir = studyPath +
"/data";
159 if (!DirectoryExists(dataDir)) {
160 mkdir(dataDir.c_str(), 0755);
168 std::string safeName = studyName;
169 for (
char& c : safeName) {
170 if (c ==
' ') c =
'-';
171 else c = std::tolower(c);
175 auto now = std::time(
nullptr);
176 auto tm = *std::localtime(&now);
178 std::ostringstream oss;
179 oss << safeName <<
"_v" << version <<
"_"
180 << std::put_time(&tm,
"%Y-%m-%d");
197 info.
version = study->GetVersion();
199 info.
author = study->GetAuthor();
201 info.
testCount =
static_cast<int>(study->GetTests().size());
207bool SnapshotManager::CopyDirectory(
const std::string& source,
const std::string& dest,
bool excludeData) {
210 if (stat(dest.c_str(), &info) != 0) {
211 if (mkdir(dest.c_str(), 0755) != 0) {
219 for (
const auto& entry : fs::directory_iterator(source)) {
220 std::string name = entry.path().filename().string();
221 std::string sourcePath = entry.path().string();
222 std::string destPath = dest +
"/" + name;
224 bool isDirectory = entry.is_directory();
227 if (ShouldExcludeFromSnapshot(name, isDirectory) && excludeData) {
233 if (!CopyDirectory(sourcePath, destPath, excludeData)) {
239 if (!CopyFileContents(sourcePath, destPath)) {
245 }
catch (
const fs::filesystem_error&) {
252bool SnapshotManager::CopyFileContents(
const std::string& source,
const std::string& dest) {
253 std::ifstream src(source, std::ios::binary);
254 if (!src.is_open()) {
258 std::ofstream dst(dest, std::ios::binary);
259 if (!dst.is_open()) {
270 struct stat fileInfo;
271 if (stat(source.c_str(), &fileInfo) == 0) {
272 chmod(dest.c_str(), fileInfo.st_mode);
279bool SnapshotManager::DirectoryExists(
const std::string& path)
const {
281 return (stat(path.c_str(), &info) == 0 && (info.st_mode & S_IFDIR));
284bool SnapshotManager::FileExists(
const std::string& path)
const {
286 return (stat(path.c_str(), &info) == 0 && !(info.st_mode & S_IFDIR));
289std::string SnapshotManager::GetCurrentDateString()
const {
290 auto now = std::time(
nullptr);
291 auto tm = *std::localtime(&now);
293 std::ostringstream oss;
294 oss << std::put_time(&tm,
"%Y-%m-%d");
298bool SnapshotManager::ShouldExcludeFromSnapshot(
const std::string& name,
bool isDirectory)
const {
300 if (isDirectory && name ==
"data") {
305 if (!name.empty() && name[0] ==
'.') {
310 if (name.find(
"~") != std::string::npos) {
314 if (name.find(
".tmp") != std::string::npos) {
334 std::string jsonPath = studyPath +
"/study-info.json";
337 std::ifstream file(jsonPath);
338 if (!file.is_open()) {
343 file >> platformJson;
350 launcherJson[
"study_name"] = platformJson.value(
"study_name",
"Imported Study");
351 launcherJson[
"description"] = platformJson.value(
"description",
"");
352 launcherJson[
"author"] = platformJson.value(
"created_by",
"");
355 std::string versionStr = platformJson.value(
"version",
"1");
358 size_t lastSpace = versionStr.rfind(
' ');
359 if (lastSpace != std::string::npos) {
360 std::string numPart = versionStr.substr(lastSpace + 1);
361 versionInt = std::atoi(numPart.c_str());
362 if (versionInt == 0) versionInt = 1;
364 launcherJson[
"version"] = versionInt;
367 json testsArray = json::array();
368 if (platformJson.contains(
"tests") && platformJson[
"tests"].is_array()) {
369 for (
const auto& platformTest : platformJson[
"tests"]) {
373 std::string testId = platformTest.value(
"test_id",
"");
374 std::string testDisplayName = platformTest.value(
"test_name", testId);
376 launcherTest[
"test_name"] = testId;
377 launcherTest[
"display_name"] = testDisplayName;
378 launcherTest[
"test_path"] = testId;
379 launcherTest[
"included"] =
true;
382 json paramVariants = json::object();
383 std::string paramsDir = studyPath +
"/tests/" + testId +
"/params";
384 if (DirectoryExists(paramsDir)) {
387 defaultVariant[
"description"] =
"Default parameters";
388 defaultVariant[
"file"] =
nullptr;
389 paramVariants[
"default"] = defaultVariant;
392 for (
const auto& entry : fs::directory_iterator(paramsDir)) {
393 if (entry.is_regular_file()) {
394 std::string filename = entry.path().filename().string();
396 if (filename.size() > 9 && filename.substr(filename.size() - 9) ==
".par.json") {
398 std::string variantName = filename.substr(0, filename.size() - 9);
400 variant[
"description"] = variantName;
401 variant[
"file"] = filename;
402 paramVariants[variantName] = variant;
403 printf(
"Found parameter variant: %s -> %s\n", variantName.c_str(), filename.c_str());
408 launcherTest[
"parameter_variants"] = paramVariants;
410 testsArray.push_back(launcherTest);
413 launcherJson[
"tests"] = testsArray;
417 launcherJson[
"study_token"] =
"";
418 launcherJson[
"upload_server_url"] =
"";
419 launcherJson[
"created_date"] = platformJson.value(
"created_at",
"");
420 launcherJson[
"modified_date"] = platformJson.value(
"created_at",
"");
423 std::ofstream outFile(jsonPath);
424 if (!outFile.is_open()) {
428 outFile << launcherJson.dump(2);
431 printf(
"Converted snapshot format to launcher format\n");
434 }
catch (
const std::exception& e) {
435 printf(
"Error converting snapshot format: %s\n", e.what());
SnapshotInfo GetSnapshotInfo(const std::string &snapshotPath)
std::string CreateSnapshot(const std::string &studyPath, const std::string &snapshotsDir)
static std::string GenerateSnapshotName(const std::string &studyName, int version)
bool ConvertSnapshotFormat(const std::string &studyPath)
bool ImportSnapshot(const std::string &snapshotPath, const std::string &studiesDir, const std::string &newStudyName)
ValidationResult ValidateSnapshot(const std::string &snapshotPath)
static std::shared_ptr< Study > LoadFromDirectory(const std::string &path)
std::vector< std::string > errors
std::vector< std::string > warnings