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

#include <ScaleManager.h>

Classes

struct  LooseOSDEntry
 
struct  ScaleMetadata
 

Public Member Functions

 ScaleManager (const std::string &batteryPath, const std::string &workspacePath="")
 
 ~ScaleManager ()
 
std::vector< std::string > GetAvailableScales ()
 
std::shared_ptr< ScaleDefinitionCreateScale (const std::string &code)
 
std::shared_ptr< ScaleDefinitionLoadScale (const std::string &code)
 
bool SaveScale (std::shared_ptr< ScaleDefinition > scale)
 
bool DeleteScale (const std::string &code)
 
bool ExportToBattery (std::shared_ptr< ScaleDefinition > scale)
 
std::shared_ptr< ScaleDefinitionImportFromFile (const std::string &filePath)
 
std::vector< LooseOSDEntryGetLooseOSDEntries () const
 
std::shared_ptr< ScaleDefinitionInstallLooseOSD (const std::string &osdPath)
 
std::string GetDefinitionPath (const std::string &code) const
 
std::string GetOSDPath (const std::string &code) const
 
std::string GetTranslationPath (const std::string &code, const std::string &lang) const
 
std::string GetBatteryPath () const
 
std::string GetWorkspacePath () const
 
std::string GetBatteryScalesPath () const
 
std::string GetWorkspaceScalesPath () const
 
std::string GetScalesPath () const
 
std::string GetDefinitionsPath () const
 
bool ScaleExists (const std::string &code) const
 
ScaleMetadata GetScaleMetadata (const std::string &code) const
 
bool CreateStudyFromScale (std::shared_ptr< ScaleDefinition > scale, const std::string &workspaceStudiesPath, const std::string &studyName="")
 
bool AddScaleToStudy (std::shared_ptr< ScaleDefinition > scale, const std::string &studyPath)
 

Detailed Description

Definition at line 14 of file ScaleManager.h.

Constructor & Destructor Documentation

◆ ScaleManager()

ScaleManager::ScaleManager ( const std::string &  batteryPath,
const std::string &  workspacePath = "" 
)

Definition at line 24 of file ScaleManager.cpp.

25 : mBatteryPath(batteryPath)
26 , mWorkspacePath(workspacePath)
27{
28 // Library paths: media/apps/scales/ (relative to PEBL root, which is parent of battery/)
29 mBatteryScalesPath = batteryPath + "/../media/apps/scales";
30 mBatteryDefinitionsPath = mBatteryScalesPath + "/definitions";
31
32 // Workspace paths (writable, user scales)
33 if (!workspacePath.empty()) {
34 mWorkspaceScalesPath = workspacePath + "/scales";
35 mWorkspaceDefinitionsPath = mWorkspaceScalesPath + "/definitions";
36 }
37
38 EnsureDirectoriesExist();
39}

◆ ~ScaleManager()

ScaleManager::~ScaleManager ( )

Definition at line 41 of file ScaleManager.cpp.

42{
43}

Member Function Documentation

◆ AddScaleToStudy()

bool ScaleManager::AddScaleToStudy ( std::shared_ptr< ScaleDefinition scale,
const std::string &  studyPath 
)

Definition at line 617 of file ScaleManager.cpp.

619{
620 if (!scale) {
621 printf("Error: Scale is null\n");
622 return false;
623 }
624
625 std::string scaleCode = scale->GetScaleInfo().code;
626 printf("Adding scale '%s' to study at: %s\n", scaleCode.c_str(), studyPath.c_str());
627
628 try {
629 // Load the existing study
630 auto study = Study::LoadFromDirectory(studyPath);
631 if (!study) {
632 printf("Error: Failed to load study from: %s\n", studyPath.c_str());
633 return false;
634 }
635
636 // Check if this scale/test already exists in the study
637 std::string testPath = studyPath + "/tests/" + scaleCode;
638 if (fs::exists(testPath)) {
639 printf("Removing existing test directory: %s\n", testPath.c_str());
640 fs::remove_all(testPath);
641 }
642
643 // Create test directory with standard PEBL structure matching ScaleRunner's expected layout:
644 // tests/{code}/
645 // {code}.pbl <- ScaleRunner.pbl (renamed)
646 // definitions/ <- {code}.json
647 // translations/ <- {code}.{lang}.json
648 // params/ <- schema + par files
649 std::string paramsPath = testPath + "/params";
650 fs::create_directories(testPath + "/definitions");
651 fs::create_directories(testPath + "/translations");
652 fs::create_directories(paramsPath);
653
654 // Copy ScaleRunner.pbl
655 std::string scaleRunnerSource = mBatteryScalesPath + "/ScaleRunner.pbl";
656 std::string scaleRunnerDest = testPath + "/" + scaleCode + ".pbl";
657
658 if (!fs::exists(scaleRunnerSource)) {
659 printf("Error: ScaleRunner.pbl not found at: %s\n", scaleRunnerSource.c_str());
660 return false;
661 }
662
663 fs::copy_file(scaleRunnerSource, scaleRunnerDest);
664
665 // Export definition to definitions/ and translations to translations/
666 if (!scale->ExportToJSON(testPath + "/definitions", testPath + "/translations")) {
667 printf("Error: Failed to export scale files\n");
668 return false;
669 }
670
671 // Create .pbl.about.txt from README.md if available, else from metadata
672 {
673 std::string aboutPath = testPath + "/" + scaleCode + ".pbl.about.txt";
674 std::string readmePath = mBatteryDefinitionsPath + "/" + scaleCode + "/README.md";
675 bool copied = false;
676
677 if (fs::exists(readmePath)) {
678 try {
679 fs::copy_file(readmePath, aboutPath, fs::copy_options::overwrite_existing);
680 copied = true;
681 } catch (const std::exception&) {}
682 }
683
684 if (!copied && !mWorkspaceScalesPath.empty()) {
685 std::string wsReadme = mWorkspaceScalesPath + "/" + scaleCode + "/README.md";
686 if (fs::exists(wsReadme)) {
687 try {
688 fs::copy_file(wsReadme, aboutPath, fs::copy_options::overwrite_existing);
689 copied = true;
690 } catch (const std::exception&) {}
691 }
692 }
693
694 if (!copied) {
695 std::ofstream aboutFile(aboutPath);
696 if (aboutFile.is_open()) {
697 const auto& info = scale->GetScaleInfo();
698 aboutFile << info.name << std::endl;
699 aboutFile << std::endl;
700 if (!info.description.empty()) {
701 aboutFile << info.description << std::endl;
702 aboutFile << std::endl;
703 }
704 if (!info.citation.empty()) {
705 aboutFile << "Citation:" << std::endl;
706 aboutFile << info.citation << std::endl;
707 }
708 aboutFile.close();
709 }
710 }
711 }
712
713 // Generate schema and parameter files from the scale's OSD parameters block
714 GenerateSchemaFiles(*scale, testPath);
715
716 // Generate screenshot for the deployed test
717 GenerateScreenshot(scaleCode, testPath);
718
719 // Add test to study (if not already present)
720 if (!study->GetTest(scaleCode)) {
721 Test test;
722 test.testName = scaleCode;
723 test.displayName = scale->GetScaleInfo().name;
724 test.testPath = scaleCode;
725 test.included = true;
726 study->AddTest(test);
727 }
728
729 // Save study
730 if (!study->Save()) {
731 printf("Error: Failed to save study-info.json\n");
732 return false;
733 }
734
735 printf("Successfully added scale '%s' to study\n", scaleCode.c_str());
736 return true;
737
738 } catch (const std::exception& e) {
739 printf("Exception while adding scale to study: %s\n", e.what());
740 return false;
741 }
742}
static std::shared_ptr< Study > LoadFromDirectory(const std::string &path)
Definition Study.cpp:100
Definition Study.h:24
std::string displayName
Definition Study.h:26
std::string testPath
Definition Study.h:27
std::string testName
Definition Study.h:25
bool included
Definition Study.h:28

References Test::displayName, Test::included, Study::LoadFromDirectory(), Test::testName, and Test::testPath.

◆ CreateScale()

std::shared_ptr< ScaleDefinition > ScaleManager::CreateScale ( const std::string &  code)

Definition at line 97 of file ScaleManager.cpp.

98{
99 return ScaleDefinition::CreateNew(code);
100}
static std::shared_ptr< ScaleDefinition > CreateNew(const std::string &code)

References ScaleDefinition::CreateNew().

◆ CreateStudyFromScale()

bool ScaleManager::CreateStudyFromScale ( std::shared_ptr< ScaleDefinition scale,
const std::string &  workspaceStudiesPath,
const std::string &  studyName = "" 
)

Definition at line 440 of file ScaleManager.cpp.

443{
444 if (!scale) {
445 printf("Error: Scale is null\n");
446 return false;
447 }
448
449 std::string scaleCode = scale->GetScaleInfo().code;
450
451 // Use provided study name, or default to scale code
452 std::string actualStudyName = studyName.empty() ? scaleCode : studyName;
453 std::string studyPath = workspaceStudiesPath + "/" + actualStudyName;
454
455 printf("Creating scale study at: %s\n", studyPath.c_str());
456
457 try {
458 // Check if study already exists - if so, remove it (overwrite mode)
459 if (fs::exists(studyPath)) {
460 printf("Removing existing study directory: %s\n", studyPath.c_str());
461 fs::remove_all(studyPath);
462 }
463
464 // Create study using Study::CreateNew (use study name, not scale code)
465 auto study = Study::CreateNew(studyPath, actualStudyName);
466 if (!study) {
467 printf("Error: Failed to create study structure\n");
468 return false;
469 }
470
471 // Set study metadata from scale
472 study->SetDescription("Scale: " + scale->GetScaleInfo().name);
473 if (!scale->GetScaleInfo().citation.empty()) {
474 study->SetAuthor(scale->GetScaleInfo().citation);
475 }
476
477 // Create test directory with standard PEBL structure matching ScaleRunner's expected layout:
478 // tests/{code}/
479 // {code}.pbl <- ScaleRunner.pbl (renamed)
480 // definitions/ <- {code}.json
481 // translations/ <- {code}.{lang}.json
482 // params/ <- schema + par files
483 std::string testPath = studyPath + "/tests/" + scaleCode;
484 std::string paramsPath = testPath + "/params";
485 fs::create_directories(testPath + "/definitions");
486 fs::create_directories(testPath + "/translations");
487 fs::create_directories(paramsPath);
488
489 // Copy ScaleRunner.pbl and rename to match scale code
490 std::string scaleRunnerSource = mBatteryScalesPath + "/ScaleRunner.pbl";
491 std::string scaleRunnerDest = testPath + "/" + scaleCode + ".pbl";
492
493 if (!fs::exists(scaleRunnerSource)) {
494 printf("Error: ScaleRunner.pbl not found at: %s\n", scaleRunnerSource.c_str());
495 return false;
496 }
497
498 fs::copy_file(scaleRunnerSource, scaleRunnerDest);
499 printf("Copied ScaleRunner.pbl to %s\n", scaleRunnerDest.c_str());
500
501 // Export definition to definitions/ and translations to translations/
502 if (!scale->ExportToJSON(testPath + "/definitions", testPath + "/translations")) {
503 printf("Error: Failed to export scale files\n");
504 return false;
505 }
506
507 printf("Exported scale definition and translations\n");
508
509 // Create .pbl.about.txt from README.md if available, else from metadata
510 {
511 std::string aboutPath = testPath + "/" + scaleCode + ".pbl.about.txt";
512 std::string readmePath = mBatteryDefinitionsPath + "/" + scaleCode + "/README.md";
513 bool copied = false;
514
515 if (fs::exists(readmePath)) {
516 try {
517 fs::copy_file(readmePath, aboutPath, fs::copy_options::overwrite_existing);
518 printf("Created about file from README.md: %s\n", aboutPath.c_str());
519 copied = true;
520 } catch (const std::exception& e) {
521 printf("Warning: Failed to copy README.md: %s\n", e.what());
522 }
523 }
524
525 // Also check workspace for README.md
526 if (!copied && !mWorkspaceScalesPath.empty()) {
527 std::string wsReadme = mWorkspaceScalesPath + "/" + scaleCode + "/README.md";
528 if (fs::exists(wsReadme)) {
529 try {
530 fs::copy_file(wsReadme, aboutPath, fs::copy_options::overwrite_existing);
531 printf("Created about file from workspace README.md: %s\n", aboutPath.c_str());
532 copied = true;
533 } catch (const std::exception& e) {
534 printf("Warning: Failed to copy workspace README.md: %s\n", e.what());
535 }
536 }
537 }
538
539 // Fallback: generate from scale metadata
540 if (!copied) {
541 std::ofstream aboutFile(aboutPath);
542 if (aboutFile.is_open()) {
543 const auto& info = scale->GetScaleInfo();
544 aboutFile << info.name << std::endl;
545 aboutFile << std::endl;
546 if (!info.description.empty()) {
547 aboutFile << info.description << std::endl;
548 aboutFile << std::endl;
549 }
550 if (!info.citation.empty()) {
551 aboutFile << "Citation:" << std::endl;
552 aboutFile << info.citation << std::endl;
553 aboutFile << std::endl;
554 }
555 if (!info.license.empty()) {
556 aboutFile << "License: " << info.license << std::endl;
557 }
558 aboutFile.close();
559 printf("Created about file from metadata: %s\n", aboutPath.c_str());
560 }
561 }
562 }
563
564 // Generate schema and parameter files from the scale's OSD parameters block
565 GenerateSchemaFiles(*scale, testPath);
566
567 // Generate screenshot for the deployed test
568 GenerateScreenshot(scaleCode, testPath);
569
570 // Add test to study
571 Test test;
572 test.testName = scaleCode; // The actual .pbl filename (e.g., "MOCI")
573 test.displayName = scale->GetScaleInfo().name; // Human-readable name
574 test.testPath = scaleCode; // Directory name (e.g., "MOCI")
575 test.included = true;
576
577 study->AddTest(test);
578
579 // Save study
580 if (!study->Save()) {
581 printf("Error: Failed to save study-info.json\n");
582 return false;
583 }
584
585 // Create default chain with the scale test
586 std::string chainPath = studyPath + "/chains/Main.json";
587 auto defaultChain = Chain::CreateNew(chainPath, "Main",
588 "Default chain for " + scale->GetScaleInfo().name);
589 if (defaultChain) {
590 // Add the scale test to the chain
591 ChainItem testItem(ItemType::Test);
592 testItem.testName = scaleCode;
593 testItem.paramVariant = "default";
594 testItem.language = "en";
595 testItem.randomGroup = 0;
596
597 defaultChain->AddItem(testItem);
598
599 if (defaultChain->Save()) {
600 printf("Created default chain with test: Main.json\n");
601 } else {
602 printf("Warning: Failed to save default chain\n");
603 }
604 } else {
605 printf("Warning: Failed to create default chain\n");
606 }
607
608 printf("Successfully created scale study: %s\n", scaleCode.c_str());
609 return true;
610
611 } catch (const std::exception& e) {
612 printf("Exception while creating scale study: %s\n", e.what());
613 return false;
614 }
615}
static std::shared_ptr< Chain > CreateNew(const std::string &path, const std::string &name, const std::string &description="")
Definition Chain.cpp:142
static std::shared_ptr< Study > CreateNew(const std::string &path, const std::string &name, const std::string &author="")
Definition Study.cpp:129

References Study::CreateNew(), Chain::CreateNew(), Test::displayName, Test::included, ChainItem::language, ChainItem::paramVariant, ChainItem::randomGroup, Test, ChainItem::testName, Test::testName, and Test::testPath.

◆ DeleteScale()

bool ScaleManager::DeleteScale ( const std::string &  code)

Definition at line 140 of file ScaleManager.cpp.

141{
142 try {
143 // Delete definition file
144 std::string defPath = GetDefinitionPath(code);
145 if (fs::exists(defPath)) {
146 fs::remove(defPath);
147 }
148
149 // Delete all translation files
150 auto transFiles = GetTranslationFiles(code);
151 for (const auto& transFile : transFiles) {
152 if (fs::exists(transFile)) {
153 fs::remove(transFile);
154 }
155 }
156
157 return true;
158 } catch (const std::exception& e) {
159 return false;
160 }
161}
std::string GetDefinitionPath(const std::string &code) const

References GetDefinitionPath().

◆ ExportToBattery()

bool ScaleManager::ExportToBattery ( std::shared_ptr< ScaleDefinition scale)

Definition at line 163 of file ScaleManager.cpp.

164{
165 return SaveScale(scale);
166}
bool SaveScale(std::shared_ptr< ScaleDefinition > scale)

References SaveScale().

◆ GetAvailableScales()

std::vector< std::string > ScaleManager::GetAvailableScales ( )

Definition at line 59 of file ScaleManager.cpp.

60{
61 std::set<std::string> scalesSet; // Use set to avoid duplicates
62
63 // Scan a directory for per-scale subdirectories containing <code>/<code>.json or <code>.osd
64 auto scanForScales = [&](const std::string& basePath) {
65 try {
66 if (!fs::exists(basePath)) return;
67 for (const auto& entry : fs::directory_iterator(basePath)) {
68 if (entry.is_directory()) {
69 std::string dirName = entry.path().filename().string();
70 std::string defFile = entry.path().string() + "/" + dirName + ".json";
71 std::string osdFile = entry.path().string() + "/" + dirName + ".osd";
72 if (fs::exists(defFile) || fs::exists(osdFile)) {
73 scalesSet.insert(dirName);
74 }
75 }
76 }
77 } catch (const std::exception& e) {
78 // Continue on error
79 }
80 };
81
82 // Scan library: media/apps/scales/definitions/<code>/<code>.json
83 scanForScales(mBatteryDefinitionsPath);
84
85 // Scan workspace: workspace/scales/<code>/<code>.json
86 if (!mWorkspacePath.empty()) {
87 scanForScales(mWorkspaceScalesPath);
88 }
89
90 // Convert set to sorted vector
91 std::vector<std::string> scales(scalesSet.begin(), scalesSet.end());
92 std::sort(scales.begin(), scales.end());
93
94 return scales;
95}

◆ GetBatteryPath()

std::string ScaleManager::GetBatteryPath ( ) const
inline

Definition at line 57 of file ScaleManager.h.

57{ return mBatteryPath; }

◆ GetBatteryScalesPath()

std::string ScaleManager::GetBatteryScalesPath ( ) const
inline

Definition at line 59 of file ScaleManager.h.

59{ return mBatteryScalesPath; }

◆ GetDefinitionPath()

std::string ScaleManager::GetDefinitionPath ( const std::string &  code) const

Definition at line 244 of file ScaleManager.cpp.

245{
246 // Check workspace: workspace/scales/<code>/<code>.json
247 if (!mWorkspaceScalesPath.empty()) {
248 std::string path = mWorkspaceScalesPath + "/" + code + "/" + code + ".json";
249 if (fs::exists(path)) return path;
250 }
251 // Check library: media/apps/scales/definitions/<code>/<code>.json
252 {
253 std::string path = mBatteryDefinitionsPath + "/" + code + "/" + code + ".json";
254 if (fs::exists(path)) return path;
255 }
256 // Return expected library path even if not found
257 return mBatteryDefinitionsPath + "/" + code + "/" + code + ".json";
258}

Referenced by DeleteScale(), GetScaleMetadata(), and ScaleExists().

◆ GetDefinitionsPath()

std::string ScaleManager::GetDefinitionsPath ( ) const
inline

Definition at line 64 of file ScaleManager.h.

64{ return mWorkspaceDefinitionsPath.empty() ? mBatteryDefinitionsPath : mWorkspaceDefinitionsPath; }

◆ GetLooseOSDEntries()

std::vector< ScaleManager::LooseOSDEntry > ScaleManager::GetLooseOSDEntries ( ) const

Definition at line 173 of file ScaleManager.cpp.

174{
175 std::vector<LooseOSDEntry> entries;
176 if (mWorkspaceScalesPath.empty()) return entries;
177
178 try {
179 if (!fs::exists(mWorkspaceScalesPath)) return entries;
180 for (const auto& entry : fs::directory_iterator(mWorkspaceScalesPath)) {
181 if (!entry.is_regular_file()) continue;
182 std::string filename = entry.path().filename().string();
183 if (filename.size() < 5 || filename.substr(filename.size() - 4) != ".osd") continue;
184
185 // Only treat as loose if there is no matching <code>/ subdirectory already
186 std::string stemCode = filename.substr(0, filename.size() - 4);
187 std::string subdirPath = mWorkspaceScalesPath + "/" + stemCode;
188 if (fs::exists(subdirPath) && fs::is_directory(subdirPath)) continue;
189
190 // Read code/name from the .osd bundle
191 try {
192 std::ifstream f(entry.path().string());
193 if (!f.is_open()) continue;
194 json bundle = json::parse(f);
195 if (!bundle.contains("definition")) continue;
196
197 LooseOSDEntry e;
198 e.path = entry.path().string();
199 if (bundle["definition"].contains("scale_info")) {
200 auto& si = bundle["definition"]["scale_info"];
201 if (si.contains("code")) e.code = si["code"].get<std::string>();
202 if (si.contains("name")) e.name = si["name"].get<std::string>();
203 }
204 if (e.code.empty()) e.code = stemCode;
205 if (e.name.empty()) e.name = e.code;
206 entries.push_back(e);
207 } catch (...) {}
208 }
209 } catch (...) {}
210
211 return entries;
212}
nlohmann::json json
Definition Chain.cpp:14

References ScaleManager::LooseOSDEntry::code, ScaleManager::LooseOSDEntry::name, and ScaleManager::LooseOSDEntry::path.

◆ GetOSDPath()

std::string ScaleManager::GetOSDPath ( const std::string &  code) const

Definition at line 260 of file ScaleManager.cpp.

261{
262 // Check workspace: workspace/scales/<code>/<code>.osd
263 if (!mWorkspaceScalesPath.empty()) {
264 std::string path = mWorkspaceScalesPath + "/" + code + "/" + code + ".osd";
265 if (fs::exists(path)) return path;
266 }
267 // Check library definitions: media/apps/scales/definitions/<code>/<code>.osd
268 {
269 std::string path = mBatteryDefinitionsPath + "/" + code + "/" + code + ".osd";
270 if (fs::exists(path)) return path;
271 }
272 return "";
273}

Referenced by GetScaleMetadata().

◆ GetScaleMetadata()

ScaleManager::ScaleMetadata ScaleManager::GetScaleMetadata ( const std::string &  code) const

Definition at line 357 of file ScaleManager.cpp.

358{
359 ScaleMetadata meta;
360 meta.code = code;
361 meta.questionCount = 0;
362
363 try {
364 std::string defPath = GetDefinitionPath(code);
365 if (fs::exists(defPath)) {
366 // Load from .json definition file
367 std::ifstream file(defPath);
368 if (!file.is_open()) return meta;
369
370 json j;
371 file >> j;
372
373 if (j.contains("scale_info")) {
374 auto& info = j["scale_info"];
375 if (info.contains("name")) meta.name = info["name"];
376 if (info.contains("description")) meta.description = info["description"];
377 if (info.contains("author")) meta.author = info["author"];
378 }
379 if (j.contains("questions") && j["questions"].is_array()) {
380 meta.questionCount = j["questions"].size();
381 } else if (j.contains("items") && j["items"].is_array()) {
382 meta.questionCount = j["items"].size();
383 }
384
385 // Get available languages from separate files
386 auto transFiles = GetTranslationFiles(code);
387 for (const auto& transFile : transFiles) {
388 fs::path p(transFile);
389 std::string filename = p.filename().string();
390 std::string prefix = code + ".pbl-";
391 if (filename.find(prefix) == 0 && filename.size() > prefix.length() + 5) {
392 std::string lang = filename.substr(prefix.length());
393 lang = lang.substr(0, lang.length() - 5);
394 meta.availableLanguages.push_back(lang);
395 }
396 }
397 } else {
398 // Fall back to .osd bundle
399 std::string osdPath = GetOSDPath(code);
400 if (osdPath.empty()) return meta;
401
402 std::ifstream file(osdPath);
403 if (!file.is_open()) return meta;
404
405 json bundle;
406 file >> bundle;
407
408 if (!bundle.contains("definition")) return meta;
409 auto& j = bundle["definition"];
410
411 if (j.contains("scale_info")) {
412 auto& info = j["scale_info"];
413 if (info.contains("name")) meta.name = info["name"];
414 if (info.contains("description")) meta.description = info["description"];
415 if (info.contains("author")) meta.author = info["author"];
416 }
417 if (j.contains("items") && j["items"].is_array()) {
418 meta.questionCount = j["items"].size();
419 } else if (j.contains("questions") && j["questions"].is_array()) {
420 meta.questionCount = j["questions"].size();
421 }
422
423 // Languages come from the translations block inside the bundle
424 if (bundle.contains("translations") && bundle["translations"].is_object()) {
425 for (auto& [lang, _] : bundle["translations"].items()) {
426 meta.availableLanguages.push_back(lang);
427 }
428 }
429 }
430
431 std::sort(meta.availableLanguages.begin(), meta.availableLanguages.end());
432
433 } catch (const std::exception& e) {
434 // Return partial metadata on error
435 }
436
437 return meta;
438}
std::string GetOSDPath(const std::string &code) const

References ScaleManager::ScaleMetadata::author, ScaleManager::ScaleMetadata::availableLanguages, ScaleManager::ScaleMetadata::code, ScaleManager::ScaleMetadata::description, GetDefinitionPath(), GetOSDPath(), ScaleManager::ScaleMetadata::name, and ScaleManager::ScaleMetadata::questionCount.

◆ GetScalesPath()

std::string ScaleManager::GetScalesPath ( ) const
inline

Definition at line 63 of file ScaleManager.h.

63{ return mWorkspaceScalesPath.empty() ? mBatteryScalesPath : mWorkspaceScalesPath; }

Referenced by SaveScale().

◆ GetTranslationPath()

std::string ScaleManager::GetTranslationPath ( const std::string &  code,
const std::string &  lang 
) const

Definition at line 275 of file ScaleManager.cpp.

276{
277 // Check workspace: new format first, then old
278 if (!mWorkspaceScalesPath.empty()) {
279 std::string newPath = mWorkspaceScalesPath + "/" + code + "/" + code + "." + lang + ".json";
280 if (fs::exists(newPath)) return newPath;
281 std::string oldPath = mWorkspaceScalesPath + "/" + code + "/" + code + ".pbl-" + lang + ".json";
282 if (fs::exists(oldPath)) return oldPath;
283 }
284 // Check library: new format first, then old
285 {
286 std::string newPath = mBatteryDefinitionsPath + "/" + code + "/" + code + "." + lang + ".json";
287 if (fs::exists(newPath)) return newPath;
288 std::string oldPath = mBatteryDefinitionsPath + "/" + code + "/" + code + ".pbl-" + lang + ".json";
289 if (fs::exists(oldPath)) return oldPath;
290 }
291 // Return expected path in new format
292 return mBatteryDefinitionsPath + "/" + code + "/" + code + "." + lang + ".json";
293}

◆ GetWorkspacePath()

std::string ScaleManager::GetWorkspacePath ( ) const
inline

Definition at line 58 of file ScaleManager.h.

58{ return mWorkspacePath; }

◆ GetWorkspaceScalesPath()

std::string ScaleManager::GetWorkspaceScalesPath ( ) const
inline

Definition at line 60 of file ScaleManager.h.

60{ return mWorkspaceScalesPath; }

◆ ImportFromFile()

std::shared_ptr< ScaleDefinition > ScaleManager::ImportFromFile ( const std::string &  filePath)

Definition at line 168 of file ScaleManager.cpp.

169{
170 return ScaleDefinition::LoadFromFile(filePath);
171}
static std::shared_ptr< ScaleDefinition > LoadFromFile(const std::string &jsonPath)

References ScaleDefinition::LoadFromFile().

◆ InstallLooseOSD()

std::shared_ptr< ScaleDefinition > ScaleManager::InstallLooseOSD ( const std::string &  osdPath)

Definition at line 214 of file ScaleManager.cpp.

215{
216 // Load the .osd to determine the scale code
217 auto scale = ScaleDefinition::LoadFromOSDFile(osdPath);
218 if (!scale) {
219 printf("InstallLooseOSD: failed to load OSD from: %s\n", osdPath.c_str());
220 return nullptr;
221 }
222 std::string code = scale->GetScaleInfo().code;
223 if (code.empty()) {
224 printf("InstallLooseOSD: scale has no code in: %s\n", osdPath.c_str());
225 return nullptr;
226 }
227
228 // Create workspace/scales/<code>/ directory
229 std::string targetDir = mWorkspaceScalesPath + "/" + code;
230 std::string targetFile = targetDir + "/" + code + ".osd";
231 try {
232 fs::create_directories(targetDir);
233 fs::copy_file(osdPath, targetFile, fs::copy_options::overwrite_existing);
234 fs::remove(osdPath); // Remove the loose file now that it is installed
235 printf("Installed OSD '%s' to: %s\n", code.c_str(), targetFile.c_str());
236 } catch (const std::exception& e) {
237 printf("InstallLooseOSD error: %s\n", e.what());
238 return nullptr;
239 }
240
241 return scale;
242}
static std::shared_ptr< ScaleDefinition > LoadFromOSDFile(const std::string &osdPath)

References ScaleDefinition::LoadFromOSDFile().

◆ LoadScale()

std::shared_ptr< ScaleDefinition > ScaleManager::LoadScale ( const std::string &  code)

Definition at line 102 of file ScaleManager.cpp.

103{
104 auto scale = std::make_shared<ScaleDefinition>();
105
106 // Try workspace first: workspace/scales/<code>/<code>.json
107 if (!mWorkspaceScalesPath.empty()) {
108 if (scale->LoadFromScalesDir(mWorkspaceScalesPath, code)) {
109 return scale;
110 }
111 }
112
113 // Fall back to library: media/apps/scales/definitions/<code>/<code>.json
114 if (scale->LoadFromScalesDir(mBatteryDefinitionsPath, code)) {
115 return scale;
116 }
117
118 return nullptr;
119}

◆ SaveScale()

bool ScaleManager::SaveScale ( std::shared_ptr< ScaleDefinition scale)

Definition at line 121 of file ScaleManager.cpp.

122{
123 if (!scale) {
124 return false;
125 }
126
127 // Save to per-scale directory in workspace: workspace/scales/<code>/
128 // Primary format is .osd (single-file bundle); no separate .json or translation files needed.
129 std::string scaleDir = GetScalesPath() + "/" + scale->GetScaleInfo().code;
130 try {
131 fs::create_directories(scaleDir);
132 } catch (const std::exception& e) {
133 printf("Error creating scale directory: %s\n", e.what());
134 return false;
135 }
136
137 return scale->ExportToOSD(scaleDir);
138}
std::string GetScalesPath() const

References GetScalesPath().

Referenced by ExportToBattery().

◆ ScaleExists()

bool ScaleManager::ScaleExists ( const std::string &  code) const

Definition at line 295 of file ScaleManager.cpp.

296{
297 return fs::exists(GetDefinitionPath(code));
298}

References GetDefinitionPath().


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