36 : mExperimentDirectory(
"")
37 , mSubjectCode(
"test")
42 , mUploadURL(
"https://peblhub.online/api/upload")
45 , mPeblExecutablePath(
"")
50 , mCurrentStudyPath(
"")
51 , mCurrentChainName(
"")
55 mExternalEditor =
"start";
57 mExternalEditor =
"open";
59 mExternalEditor =
"xdg-open";
63 mBatteryPath = DetectPEBLInstallation();
66 mExperimentDirectory = mBatteryPath;
70 char exePath[MAX_PATH];
71 DWORD len = GetModuleFileNameA(
NULL, exePath, MAX_PATH);
72 if (len > 0 && len < MAX_PATH) {
73 std::string exePathStr(exePath);
74 size_t lastSep = exePathStr.find_last_of(
"/\\");
75 if (lastSep != std::string::npos) {
76 std::string exeDir = exePathStr.substr(0, lastSep);
77 mPeblExecutablePath = exeDir +
"\\pebl2.exe";
79 if (stat(mPeblExecutablePath.c_str(), &st) == 0) {
80 printf(
"Found PEBL executable at: %s\n", mPeblExecutablePath.c_str());
82 printf(
"Warning: PEBL executable not found at: %s\n", mPeblExecutablePath.c_str());
83 mPeblExecutablePath =
"";
93 mPeblExecutablePath = std::string(exeDir) +
"/pebl2";
96 if (stat(mPeblExecutablePath.c_str(), &st) == 0) {
97 printf(
"Found PEBL executable at: %s\n", mPeblExecutablePath.c_str());
99 printf(
"Warning: PEBL executable not found at: %s\n", mPeblExecutablePath.c_str());
100 mPeblExecutablePath =
"";
107 if (IsPortableMode()) {
110 std::string portableRoot = GetPortableWorkspacePath();
114 char absPath[MAX_PATH];
115 if (GetFullPathNameA(portableRoot.c_str(), MAX_PATH, absPath,
NULL) > 0) {
116 mWorkspacePath = absPath;
118 mWorkspacePath = portableRoot;
121 char* absPath = realpath(portableRoot.c_str(),
nullptr);
123 mWorkspacePath = absPath;
126 mWorkspacePath = portableRoot;
129 printf(
"Portable mode: workspace set to %s\n", mWorkspacePath.c_str());
132 if (!mWorkspacePath.empty()) {
134 mDataOutputPath = mWorkspacePath +
"\\my_studies";
136 mDataOutputPath = mWorkspacePath +
"/my_studies";
141 std::string documentsPath = GetDocumentsPath();
142 if (!documentsPath.empty()) {
144 mWorkspacePath = documentsPath +
"\\pebl-exp." +
PEBL_VERSION;
146 mWorkspacePath = documentsPath +
"/pebl-exp." +
PEBL_VERSION;
151 if (!mWorkspacePath.empty()) {
153 mDataOutputPath = mWorkspacePath +
"\\my_studies";
155 mDataOutputPath = mWorkspacePath +
"/my_studies";
425 std::string configPath = GetConfigFilePath();
426 std::ifstream configFile(configPath);
428 if (!configFile.is_open()) {
435 bool portable = IsPortableMode();
438 std::string currentSection =
"";
440 while (std::getline(configFile, line)) {
442 if (line.empty() || line[0] ==
'#') {
447 if (line[0] ==
'[' && line[line.length()-1] ==
']') {
448 currentSection = line.substr(1, line.length()-2);
453 size_t equalPos = line.find(
'=');
454 if (equalPos == std::string::npos) {
458 std::string key = line.substr(0, equalPos);
459 std::string value = line.substr(equalPos + 1);
462 key.erase(0, key.find_first_not_of(
" \t"));
463 key.erase(key.find_last_not_of(
" \t") + 1);
464 value.erase(0, value.find_first_not_of(
" \t"));
465 value.erase(value.find_last_not_of(
" \t") + 1);
468 if (currentSection ==
"") {
471 if (key ==
"experiment_directory" || key ==
"workspace_path" ||
472 key ==
"battery_path" || key ==
"pebl_executable_path" ||
473 key ==
"data_output_path") {
479 if (key ==
"experiment_directory") {
480 mExperimentDirectory = value;
481 }
else if (key ==
"subject_code") {
482 mSubjectCode = value;
483 }
else if (key ==
"language") {
485 }
else if (key ==
"fullscreen") {
486 mFullscreen = (value ==
"true" || value ==
"1");
487 }
else if (key ==
"auto_upload") {
488 mAutoUpload = (value ==
"true" || value ==
"1");
489 }
else if (key ==
"upload_token") {
490 mUploadToken = value;
491 }
else if (key ==
"upload_url") {
493 }
else if (key ==
"workspace_path") {
494 mWorkspacePath = value;
495 }
else if (key ==
"battery_path") {
496 mBatteryPath = value;
497 }
else if (key ==
"pebl_executable_path") {
498 mPeblExecutablePath = value;
499 }
else if (key ==
"data_output_path") {
500 mDataOutputPath = value;
501 }
else if (key ==
"font_size") {
503 mFontSize = std::stoi(value);
505 printf(
"Warning: Invalid font_size value, using default\n");
507 }
else if (key ==
"window_width") {
509 mWindowWidth = std::stoi(value);
511 printf(
"Warning: Invalid window_width value, using default\n");
513 }
else if (key ==
"window_height") {
515 mWindowHeight = std::stoi(value);
517 printf(
"Warning: Invalid window_height value, using default\n");
519 }
else if (key ==
"current_study_path") {
520 if (portable && !value.empty()) {
523 if (value[0] !=
'/' && !(value.length() > 1 && value[1] ==
':')) {
526 fs::path relativePath(value);
527 fs::path absolutePath = fs::absolute(fs::path(mWorkspacePath) / relativePath);
528 mCurrentStudyPath = absolutePath.string();
530 mCurrentStudyPath = value;
534 mCurrentStudyPath =
"";
537 mCurrentStudyPath = value;
539 }
else if (key ==
"current_chain_name") {
540 mCurrentChainName = value;
541 }
else if (key ==
"external_editor") {
542 mExternalEditor = value;
544 }
else if (currentSection ==
"recent") {
546 size_t pipe1 = value.find(
'|');
547 if (pipe1 != std::string::npos) {
548 size_t pipe2 = value.find(
'|', pipe1 + 1);
549 if (pipe2 != std::string::npos) {
551 std::string timestampStr = value.substr(pipe2 + 1);
553 if (timestampStr.empty()) {
558 recent.
name = value.substr(0, pipe1);
559 recent.
path = value.substr(pipe1 + 1, pipe2 - pipe1 - 1);
560 recent.
lastRun = std::stol(timestampStr);
561 mRecentExperiments.push_back(recent);
562 }
catch (
const std::invalid_argument& e) {
564 printf(
"Warning: Skipping malformed recent experiment entry: %s\n", value.c_str());
565 }
catch (
const std::out_of_range& e) {
567 printf(
"Warning: Skipping out-of-range timestamp in recent experiment entry: %s\n", value.c_str());
581 if (!mBatteryPath.empty() &&
582 !(stat(mBatteryPath.c_str(), &st) == 0 && S_ISDIR(st.st_mode)))
584 printf(
"Warning: Saved battery_path no longer exists: %s\n", mBatteryPath.c_str());
585 printf(
"Re-detecting PEBL installation...\n");
586 mBatteryPath = DetectPEBLInstallation();
587 if (!mBatteryPath.empty()) {
588 printf(
"Re-detected battery at: %s\n", mBatteryPath.c_str());
590 printf(
"Warning: Could not auto-detect battery path.\n");
594 if (!mPeblExecutablePath.empty() &&
595 !(stat(mPeblExecutablePath.c_str(), &st) == 0 && S_ISREG(st.st_mode)))
597 printf(
"Warning: Saved pebl_executable_path no longer exists: %s\n", mPeblExecutablePath.c_str());
599 if (!mBatteryPath.empty()) {
601 std::string candidate = mBatteryPath +
"\\..\\bin\\pebl2.exe";
603 std::string candidate = mBatteryPath +
"/../bin/pebl2";
605 if (stat(candidate.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
606 mPeblExecutablePath = candidate;
607 printf(
"Re-detected PEBL executable at: %s\n", mPeblExecutablePath.c_str());
609 printf(
"Warning: Could not auto-detect PEBL executable path.\n");
610 mPeblExecutablePath =
"";
620 std::string configPath = GetConfigFilePath();
621 bool portable = IsPortableMode();
624 fs::path configDir = fs::path(configPath).parent_path();
625 if (!configDir.empty()) {
627 fs::create_directories(configDir);
628 }
catch (
const fs::filesystem_error& e) {
629 printf(
"Warning: Could not create config directory: %s\n", e.what());
633 std::ofstream configFile(configPath);
634 if (!configFile.is_open()) {
639 configFile <<
"# PEBL Launcher Configuration" << std::endl;
640 configFile <<
"# Auto-generated - edit at your own risk" << std::endl;
642 configFile <<
"# Portable mode - path settings are auto-detected on launch" << std::endl;
644 configFile << std::endl;
649 configFile <<
"experiment_directory=" << mExperimentDirectory << std::endl;
651 configFile <<
"subject_code=" << mSubjectCode << std::endl;
652 configFile <<
"language=" << mLanguage << std::endl;
653 configFile <<
"fullscreen=" << (mFullscreen ?
"true" :
"false") << std::endl;
654 configFile << std::endl;
658 configFile <<
"# File paths" << std::endl;
659 configFile <<
"workspace_path=" << mWorkspacePath << std::endl;
660 configFile <<
"battery_path=" << mBatteryPath << std::endl;
661 configFile <<
"pebl_executable_path=" << mPeblExecutablePath << std::endl;
662 configFile <<
"data_output_path=" << mDataOutputPath << std::endl;
663 configFile << std::endl;
666 configFile <<
"# Upload settings" << std::endl;
667 configFile <<
"auto_upload=" << (mAutoUpload ?
"true" :
"false") << std::endl;
668 configFile <<
"upload_token=" << mUploadToken << std::endl;
669 configFile <<
"upload_url=" << mUploadURL << std::endl;
670 configFile << std::endl;
672 configFile <<
"# UI settings" << std::endl;
673 configFile <<
"font_size=" << mFontSize << std::endl;
674 configFile <<
"window_width=" << mWindowWidth << std::endl;
675 configFile <<
"window_height=" << mWindowHeight << std::endl;
676 configFile <<
"external_editor=" << mExternalEditor << std::endl;
677 configFile << std::endl;
679 configFile <<
"# Session state" << std::endl;
681 if (!mCurrentStudyPath.empty()) {
685 fs::path studyPath(mCurrentStudyPath);
686 fs::path workspacePath(mWorkspacePath);
687 fs::path relativePath = fs::relative(studyPath, workspacePath);
688 configFile <<
"current_study_path=" << relativePath.string() << std::endl;
691 printf(
"Warning: Could not convert study path to relative path\n");
694 configFile <<
"current_study_path=" << mCurrentStudyPath << std::endl;
697 configFile <<
"current_chain_name=" << mCurrentChainName << std::endl;
700 if (!mRecentExperiments.empty()) {
701 configFile << std::endl;
702 configFile <<
"[recent]" << std::endl;
703 for (
size_t i = 0; i < mRecentExperiments.size(); i++) {
705 configFile <<
"recent" << i <<
"=" << recent.
name <<
"|"
706 << recent.
path <<
"|" << recent.
lastRun << std::endl;