PEBL 2.2
Psychology Experiment Building Language - Cross-platform psychological experiment development system
Study Class Reference

#include <Study.h>

Classes

struct  ValidationResult
 

Public Member Functions

 Study ()
 
 ~Study ()
 
bool Save ()
 
const std::string & GetName () const
 
const std::string & GetDescription () const
 
int GetVersion () const
 
const std::string & GetAuthor () const
 
const std::string & GetStudyToken () const
 
const std::string & GetUploadServerURL () const
 
bool GetUploadEnabled () const
 
const std::string & GetCreatedDate () const
 
const std::string & GetModifiedDate () const
 
const std::string & GetPath () const
 
const std::vector< Test > & GetTests () const
 
std::string GetStudyCode () const
 
void SetName (const std::string &name)
 
void SetDescription (const std::string &desc)
 
void SetAuthor (const std::string &author)
 
void SetStudyToken (const std::string &token)
 
void SetUploadServerURL (const std::string &url)
 
void SetUploadEnabled (bool enabled)
 
void IncrementVersion ()
 
bool AddTest (const Test &test)
 
bool RemoveTest (const std::string &testName)
 
TestGetTest (const std::string &testName)
 
const TestGetTest (const std::string &testName) const
 
std::vector< std::string > GetChainFiles () const
 
int GetChainCount () const
 
bool CreateUploadConfigForTest (const std::string &testName)
 
std::string GetUploadConfigPath (const std::string &testName) const
 
bool TestHasUploadConfig (const std::string &testName) const
 
ValidationResult Validate () const
 

Static Public Member Functions

static std::shared_ptr< StudyLoadFromDirectory (const std::string &path)
 
static std::shared_ptr< StudyCreateNew (const std::string &path, const std::string &name, const std::string &author="")
 

Detailed Description

Definition at line 44 of file Study.h.

Constructor & Destructor Documentation

◆ Study()

Study::Study ( )

Definition at line 92 of file Study.cpp.

93 : mVersion(1), mUploadEnabled(false)
94{
95}

◆ ~Study()

Study::~Study ( )

Definition at line 97 of file Study.cpp.

97 {
98}

Member Function Documentation

◆ AddTest()

bool Study::AddTest ( const Test test)

Definition at line 161 of file Study.cpp.

161 {
162 // Check if test already exists
163 for (const auto& t : mTests) {
164 if (t.testName == test.testName) {
165 return false; // Already exists
166 }
167 }
168
169 mTests.push_back(test);
170 UpdateModifiedDate();
171 return true;
172}
std::string testName
Definition Study.h:25

References Test::testName.

◆ CreateNew()

std::shared_ptr< Study > Study::CreateNew ( const std::string &  path,
const std::string &  name,
const std::string &  author = "" 
)
static

Definition at line 129 of file Study.cpp.

131 {
132 auto study = std::make_shared<Study>();
133 study->mPath = path;
134 study->mName = name;
135 study->mAuthor = author;
136 study->mVersion = 1;
137 study->mCreatedDate = study->GetCurrentISO8601Time();
138 study->mModifiedDate = study->mCreatedDate;
139
140 // Create directory structure using filesystem API (handles paths with spaces)
141 try {
142 fs::create_directories(path);
143 fs::create_directories(fs::path(path) / "chains");
144 fs::create_directories(fs::path(path) / "tests");
145 // Note: data/ directory removed - not needed in study structure
146 } catch (const fs::filesystem_error& e) {
147 printf("Error creating study directories: %s\n", e.what());
148 }
149
150 // Create default chain
151 std::string chainPath = path + "/chains/Main.json";
152 auto defaultChain = Chain::CreateNew(chainPath, "Main", "Default chain for this study");
153 if (defaultChain) {
154 defaultChain->Save();
155 printf("Created default chain: Main.json\n");
156 }
157
158 return study;
159}
static std::shared_ptr< Chain > CreateNew(const std::string &path, const std::string &name, const std::string &description="")
Definition Chain.cpp:142

References Chain::CreateNew().

Referenced by ScaleManager::CreateStudyFromScale().

◆ CreateUploadConfigForTest()

bool Study::CreateUploadConfigForTest ( const std::string &  testName)

Definition at line 458 of file Study.cpp.

458 {
459 // Validate inputs
460 if (mStudyToken.empty()) {
461 printf("Cannot create upload config: study token not set\n");
462 return false;
463 }
464
465 if (mUploadServerURL.empty()) {
466 printf("Cannot create upload config: upload server URL not set\n");
467 return false;
468 }
469
470 // Get test
471 const Test* test = GetTest(testName);
472 if (!test) {
473 printf("Cannot create upload config: test not found: %s\n", testName.c_str());
474 return false;
475 }
476
477 // Parse server URL to extract host, port, path
478 std::string host, page;
479 int port = 443; // Default HTTPS port
480
481 std::string url = mUploadServerURL;
482
483 // Remove protocol
484 if (url.find("https://") == 0) {
485 url = url.substr(8);
486 port = 443;
487 } else if (url.find("http://") == 0) {
488 url = url.substr(7);
489 port = 80;
490 }
491
492 // Find first slash to separate host from path
493 size_t slashPos = url.find('/');
494 if (slashPos != std::string::npos) {
495 host = url.substr(0, slashPos);
496 page = url.substr(slashPos);
497 } else {
498 host = url;
499 page = "/api/upload.php"; // Default page
500 }
501
502 // Check for port in host (host:port format)
503 size_t colonPos = host.find(':');
504 if (colonPos != std::string::npos) {
505 std::string portStr = host.substr(colonPos + 1);
506 port = std::atoi(portStr.c_str());
507 host = host.substr(0, colonPos);
508 }
509
510 // Create upload.json content
511 try {
512 json j;
513 j["host"] = host;
514 j["page"] = page;
515 j["subnumpage"] = "/api/getNewParticipantId.php"; // For server-assigned participant IDs
516 j["port"] = port;
517 j["token"] = mStudyToken;
518 j["taskname"] = testName;
519 j["username"] = mStudyToken;
520 j["uploadpassword"] = mStudyToken;
521
522 // Write to file
523 std::string uploadPath = GetUploadConfigPath(testName);
524
525 // Ensure test directory exists
526 std::string testDir = mPath + "/tests/" + testName;
527 try {
528 fs::create_directories(testDir);
529 } catch (const fs::filesystem_error& e) {
530 printf("Error creating test directory: %s\n", e.what());
531 return false;
532 }
533
534 std::ofstream file(uploadPath);
535 if (!file.is_open()) {
536 printf("Failed to create upload config file: %s\n", uploadPath.c_str());
537 return false;
538 }
539
540 file << j.dump(2); // Pretty print with 2-space indent
541 file.close();
542
543 printf("Created upload config: %s\n", uploadPath.c_str());
544 return true;
545
546 } catch (const std::exception& e) {
547 printf("Error creating upload config: %s\n", e.what());
548 return false;
549 }
550}
nlohmann::json json
Definition Chain.cpp:14
Test * GetTest(const std::string &testName)
Definition Study.cpp:187
std::string GetUploadConfigPath(const std::string &testName) const
Definition Study.cpp:448
Definition Study.h:24

References GetTest(), and GetUploadConfigPath().

◆ GetAuthor()

const std::string & Study::GetAuthor ( ) const
inline

Definition at line 64 of file Study.h.

64{ return mAuthor; }

◆ GetChainCount()

int Study::GetChainCount ( ) const

Definition at line 235 of file Study.cpp.

235 {
236 return static_cast<int>(GetChainFiles().size());
237}
std::vector< std::string > GetChainFiles() const
Definition Study.cpp:209

References GetChainFiles().

◆ GetChainFiles()

std::vector< std::string > Study::GetChainFiles ( ) const

Definition at line 209 of file Study.cpp.

209 {
210 std::vector<std::string> chains;
211 std::string chainsDir = mPath + "/chains";
212
213 try {
214 if (!fs::exists(chainsDir) || !fs::is_directory(chainsDir)) {
215 return chains;
216 }
217
218 for (const auto& entry : fs::directory_iterator(chainsDir)) {
219 if (!entry.is_regular_file()) continue;
220
221 std::string filename = entry.path().filename().string();
222
223 // Look for .json files
224 if (filename.size() > 5 && filename.substr(filename.size() - 5) == ".json") {
225 chains.push_back(filename);
226 }
227 }
228 } catch (const fs::filesystem_error&) {
229 // Directory doesn't exist or can't be read
230 }
231
232 return chains;
233}

Referenced by GetChainCount().

◆ GetCreatedDate()

const std::string & Study::GetCreatedDate ( ) const
inline

Definition at line 68 of file Study.h.

68{ return mCreatedDate; }

◆ GetDescription()

const std::string & Study::GetDescription ( ) const
inline

Definition at line 62 of file Study.h.

62{ return mDescription; }

◆ GetModifiedDate()

const std::string & Study::GetModifiedDate ( ) const
inline

Definition at line 69 of file Study.h.

69{ return mModifiedDate; }

◆ GetName()

const std::string & Study::GetName ( ) const
inline

Definition at line 61 of file Study.h.

61{ return mName; }

◆ GetPath()

const std::string & Study::GetPath ( ) const
inline

Definition at line 70 of file Study.h.

70{ return mPath; }

Referenced by Chain::Validate().

◆ GetStudyCode()

std::string Study::GetStudyCode ( ) const

Definition at line 422 of file Study.cpp.

422 {
423 std::string code;
424 code.reserve(4);
425
426 // Extract first 4 alphanumeric characters from study name
427 for (char c : mName) {
428 if (std::isalnum(static_cast<unsigned char>(c))) {
429 code += std::toupper(static_cast<unsigned char>(c));
430 if (code.length() >= 4) {
431 break;
432 }
433 }
434 }
435
436 // Pad with 'X' if less than 4 characters
437 while (code.length() < 4) {
438 code += 'X';
439 }
440
441 return code;
442}

◆ GetStudyToken()

const std::string & Study::GetStudyToken ( ) const
inline

Definition at line 65 of file Study.h.

65{ return mStudyToken; }

◆ GetTest() [1/2]

Test * Study::GetTest ( const std::string &  testName)

Definition at line 187 of file Study.cpp.

187 {
188 auto it = std::find_if(mTests.begin(), mTests.end(),
189 [&testName](const Test& t) { return t.testName == testName; });
190
191 if (it != mTests.end()) {
192 return &(*it);
193 }
194
195 return nullptr;
196}

Referenced by CreateUploadConfigForTest(), and Chain::Validate().

◆ GetTest() [2/2]

const Test * Study::GetTest ( const std::string &  testName) const

Definition at line 198 of file Study.cpp.

198 {
199 auto it = std::find_if(mTests.begin(), mTests.end(),
200 [&testName](const Test& t) { return t.testName == testName; });
201
202 if (it != mTests.end()) {
203 return &(*it);
204 }
205
206 return nullptr;
207}

◆ GetTests()

const std::vector< Test > & Study::GetTests ( ) const
inline

Definition at line 71 of file Study.h.

71{ return mTests; }

◆ GetUploadConfigPath()

std::string Study::GetUploadConfigPath ( const std::string &  testName) const

Definition at line 448 of file Study.cpp.

448 {
449 return mPath + "/tests/" + testName + "/upload.json";
450}

Referenced by CreateUploadConfigForTest(), and TestHasUploadConfig().

◆ GetUploadEnabled()

bool Study::GetUploadEnabled ( ) const
inline

Definition at line 67 of file Study.h.

67{ return mUploadEnabled; }

◆ GetUploadServerURL()

const std::string & Study::GetUploadServerURL ( ) const
inline

Definition at line 66 of file Study.h.

66{ return mUploadServerURL; }

◆ GetVersion()

int Study::GetVersion ( ) const
inline

Definition at line 63 of file Study.h.

63{ return mVersion; }

◆ IncrementVersion()

void Study::IncrementVersion ( )
inline

Definition at line 83 of file Study.h.

83{ mVersion++; UpdateModifiedDate(); }

◆ LoadFromDirectory()

std::shared_ptr< Study > Study::LoadFromDirectory ( const std::string &  path)
static

Definition at line 100 of file Study.cpp.

100 {
101 auto study = std::make_shared<Study>();
102 study->mPath = path;
103
104 std::string jsonPath = path + "/study-info.json";
105 printf(" Looking for: %s\n", jsonPath.c_str());
106
107 // Check if file exists
108 std::ifstream testFile(jsonPath);
109 if (!testFile.good()) {
110 printf(" File does not exist or cannot be opened\n");
111 return nullptr;
112 }
113 testFile.close();
114
115 if (!study->LoadFromJSON(jsonPath)) {
116 printf(" Failed to parse JSON from file\n");
117 return nullptr;
118 }
119
120 printf(" Successfully loaded study: %s\n", study->GetName().c_str());
121 return study;
122}

Referenced by ScaleManager::AddScaleToStudy(), SnapshotManager::CreateSnapshot(), SnapshotManager::GetSnapshotInfo(), and SnapshotManager::ValidateSnapshot().

◆ RemoveTest()

bool Study::RemoveTest ( const std::string &  testName)

Definition at line 174 of file Study.cpp.

174 {
175 auto it = std::find_if(mTests.begin(), mTests.end(),
176 [&testName](const Test& t) { return t.testName == testName; });
177
178 if (it != mTests.end()) {
179 mTests.erase(it);
180 UpdateModifiedDate();
181 return true;
182 }
183
184 return false;
185}

◆ Save()

bool Study::Save ( )

Definition at line 124 of file Study.cpp.

124 {
125 std::string jsonPath = mPath + "/study-info.json";
126 return SaveToJSON(jsonPath);
127}

◆ SetAuthor()

void Study::SetAuthor ( const std::string &  author)
inline

Definition at line 79 of file Study.h.

79{ mAuthor = author; UpdateModifiedDate(); }

◆ SetDescription()

void Study::SetDescription ( const std::string &  desc)
inline

Definition at line 78 of file Study.h.

78{ mDescription = desc; UpdateModifiedDate(); }

◆ SetName()

void Study::SetName ( const std::string &  name)
inline

Definition at line 77 of file Study.h.

77{ mName = name; UpdateModifiedDate(); }

◆ SetStudyToken()

void Study::SetStudyToken ( const std::string &  token)
inline

Definition at line 80 of file Study.h.

80{ mStudyToken = token; UpdateModifiedDate(); }

◆ SetUploadEnabled()

void Study::SetUploadEnabled ( bool  enabled)
inline

Definition at line 82 of file Study.h.

82{ mUploadEnabled = enabled; UpdateModifiedDate(); }

◆ SetUploadServerURL()

void Study::SetUploadServerURL ( const std::string &  url)
inline

Definition at line 81 of file Study.h.

81{ mUploadServerURL = url; UpdateModifiedDate(); }

◆ TestHasUploadConfig()

bool Study::TestHasUploadConfig ( const std::string &  testName) const

Definition at line 452 of file Study.cpp.

452 {
453 std::string uploadPath = GetUploadConfigPath(testName);
454 struct stat info;
455 return (stat(uploadPath.c_str(), &info) == 0);
456}

References GetUploadConfigPath().

◆ Validate()

Study::ValidationResult Study::Validate ( ) const

Definition at line 239 of file Study.cpp.

239 {
240 ValidationResult result;
241
242 // Check required fields
243 if (mName.empty()) {
244 result.errors.push_back("Study name is required");
245 }
246
247 if (mPath.empty()) {
248 result.errors.push_back("Study path is required");
249 }
250
251 if (mVersion < 1) {
252 result.errors.push_back("Study version must be >= 1");
253 }
254
255 // Check directory structure
256 struct stat info;
257 if (stat(mPath.c_str(), &info) != 0 || !(info.st_mode & S_IFDIR)) {
258 result.errors.push_back("Study directory does not exist: " + mPath);
259 } else {
260 // Check subdirectories
261 std::string chainsDir = mPath + "/chains";
262 if (stat(chainsDir.c_str(), &info) != 0 || !(info.st_mode & S_IFDIR)) {
263 result.warnings.push_back("chains/ directory missing");
264 }
265
266 std::string testsDir = mPath + "/tests";
267 if (stat(testsDir.c_str(), &info) != 0 || !(info.st_mode & S_IFDIR)) {
268 result.warnings.push_back("tests/ directory missing");
269 }
270 }
271
272 // Validate tests
273 for (const auto& test : mTests) {
274 if (test.testName.empty()) {
275 result.errors.push_back("Test name cannot be empty");
276 }
277
278 if (test.testPath.empty()) {
279 result.errors.push_back("Test path cannot be empty for test: " + test.testName);
280 }
281
282 // Check if test directory exists
283 if (!test.Exists(mPath)) {
284 result.warnings.push_back("Test directory not found: " + test.testPath);
285 }
286 }
287
288 return result;
289}

References Study::ValidationResult::errors, and Study::ValidationResult::warnings.


The documentation for this class was generated from the following files: