1 #include "otsdaq-core/CodeEditor/CodeEditor.h"
2 #include "otsdaq-core/CgiDataUtilities/CgiDataUtilities.h"
3 #include "otsdaq-core/XmlUtilities/HttpXmlDocument.h"
12 #define CODE_EDITOR_DATA_PATH \
13 std::string(getenv("SERVICE_DATA_PATH")) + "/" + "CodeEditorData"
16 #define __MF_SUBJECT__ "CodeEditor"
18 const std::string CodeEditor::SPECIAL_TYPE_FEInterface =
"FEInterface";
19 const std::string CodeEditor::SPECIAL_TYPE_DataProcessor =
"DataProcessor";
20 const std::string CodeEditor::SPECIAL_TYPE_Table =
"Table";
21 const std::string CodeEditor::SPECIAL_TYPE_ControlsInterface =
"ControlsInterface";
22 const std::string CodeEditor::SPECIAL_TYPE_Tools =
"Tools";
23 const std::string CodeEditor::SPECIAL_TYPE_UserData =
"UserData";
24 const std::string CodeEditor::SPECIAL_TYPE_OutputData =
"OutputData";
26 const std::string CodeEditor::SOURCE_BASE_PATH = std::string(getenv(
"MRB_SOURCE")) +
"/";
27 const std::string CodeEditor::USER_DATA_PATH = std::string(getenv(
"USER_DATA")) +
"/";
28 const std::string CodeEditor::OTSDAQ_DATA_PATH = std::string(getenv(
"OTSDAQ_DATA")) +
"/";
32 CodeEditor::CodeEditor()
33 : ALLOWED_FILE_EXTENSIONS_({
"h",
52 std::string path = CODE_EDITOR_DATA_PATH;
53 DIR* dir = opendir(path.c_str());
56 else if(-1 == mkdir(path.c_str(), 0755))
59 __SS__ <<
"Service directory creation failed: " << path << std::endl;
68 void CodeEditor::xmlRequest(
const std::string& option,
71 const std::string& username)
try
85 if(option ==
"getDirectoryContent")
87 getDirectoryContent(cgiIn, xmlOut);
89 else if(option ==
"getFileContent")
91 getFileContent(cgiIn, xmlOut);
93 else if(option ==
"saveFileContent")
95 saveFileContent(cgiIn, xmlOut, username);
97 else if(option ==
"build")
99 build(cgiIn, xmlOut, username);
101 else if(option ==
"getAllowedExtensions")
103 xmlOut->addTextElementToData(
105 StringMacros::setToString(ALLOWED_FILE_EXTENSIONS_,
","));
109 __SS__ <<
"Unrecognized request option '" << option <<
".'" << __E__;
113 catch(
const std::runtime_error& e)
115 __SS__ <<
"Error encountered while handling the Code Editor request option '"
116 << option <<
"': " << e.what() << __E__;
117 xmlOut->addTextElementToData(
"Error", ss.str());
121 __SS__ <<
"Unknown error encountered while handling the Code Editor request option '"
122 << option <<
"!'" << __E__;
123 xmlOut->addTextElementToData(
"Error", ss.str());
128 std::string CodeEditor::safePathString(
const std::string& path)
132 std::string fullpath =
"";
133 for(
unsigned int i = 0; i < path.length(); ++i)
134 if((path[i] >=
'a' && path[i] <=
'z') || (path[i] >=
'A' && path[i] <=
'Z') ||
135 path[i] >=
'_' || path[i] >=
'-' || path[i] >=
' ' || path[i] >=
'/')
138 if(!fullpath.length())
140 __SS__ <<
"Invalid path '" << fullpath <<
"' found!" << __E__;
149 std::string CodeEditor::safeExtensionString(
const std::string& extension)
153 std::string retExt =
"";
155 for(
unsigned int i = 0; i < extension.length(); ++i)
156 if((extension[i] >=
'a' && extension[i] <=
'z'))
157 retExt += extension[i];
158 else if((extension[i] >=
'A' && extension[i] <=
'Z'))
159 retExt += extension[i] + 32;
163 if(ALLOWED_FILE_EXTENSIONS_.find(
165 ALLOWED_FILE_EXTENSIONS_.end())
167 __SS__ <<
"Invalid extension '" << retExt <<
"' found!" << __E__;
175 void CodeEditor::getDirectoryContent(cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut)
177 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
178 path = safePathString(CgiDataUtilities::decodeURIComponent(path));
180 __COUTV__(CodeEditor::SOURCE_BASE_PATH);
182 xmlOut->addTextElementToData(
"path", path);
184 const unsigned int numOfTypes = 7;
185 std::string specialTypeNames[] = {
"Front-End Plugins",
186 "Data Processor Plugins",
187 "Configuration Table Plugins",
188 "Controls Interface Plugins",
192 std::string specialTypes[] = {SPECIAL_TYPE_FEInterface,
193 SPECIAL_TYPE_DataProcessor,
195 SPECIAL_TYPE_ControlsInterface,
197 SPECIAL_TYPE_UserData,
198 SPECIAL_TYPE_OutputData};
200 std::string pathMatchPrepend =
"/";
202 if(path.length() > 1 && path[1] ==
'/')
203 pathMatchPrepend +=
'/';
205 for(
unsigned int i = 0; i < numOfTypes; ++i)
206 if(path == pathMatchPrepend + specialTypeNames[i])
208 __COUT__ <<
"Getting all " << specialTypeNames[i] <<
"..." << __E__;
212 if(specialTypes[i] == SPECIAL_TYPE_UserData)
214 getPathContent(
"/", CodeEditor::USER_DATA_PATH, xmlOut);
217 else if(specialTypes[i] == SPECIAL_TYPE_OutputData)
219 getPathContent(
"/", CodeEditor::OTSDAQ_DATA_PATH, xmlOut);
223 std::map<std::string ,
224 std::set<std::string> >
225 retMap = CodeEditor::getSpecialsMap();
226 if(retMap.find(specialTypes[i]) != retMap.end())
228 for(
const auto& specialTypeFile : retMap[specialTypes[i]])
230 xmlOut->addTextElementToData(
"specialFile", specialTypeFile);
235 __SS__ <<
"No files for type '" << specialTypeNames[i] <<
"' were found."
244 for(
unsigned int i = 0; i < numOfTypes; ++i)
245 xmlOut->addTextElementToData(
"special", specialTypeNames[i]);
247 std::string contents;
249 if((i = path.find(
"$USER_DATA/")) == 0 ||
250 (i == 1 && path[0] ==
'/'))
251 getPathContent(CodeEditor::USER_DATA_PATH,
252 path.substr(std::string(
"/$USER_DATA/").size()),
254 else if((i = path.find(
"$OTSDAQ_DATA/")) == 0 ||
255 (i == 1 && path[0] ==
'/'))
256 getPathContent(CodeEditor::OTSDAQ_DATA_PATH,
257 path.substr(std::string(
"/$OTSDAQ_DATA/").size()),
260 getPathContent(CodeEditor::SOURCE_BASE_PATH, path, xmlOut);
266 void CodeEditor::getPathContent(
const std::string& basepath,
267 const std::string& path,
271 struct dirent* entry;
276 if(!(pDIR = opendir((basepath + path).c_str())))
278 __SS__ <<
"Path '" << path <<
"' could not be opened!" << __E__;
284 struct InsensitiveCompare
286 bool operator()(
const std::string& as,
const std::string& bs)
const
289 const char* a = as.c_str();
290 const char* b = bs.c_str();
294 while((d = (std::toupper(*a) - std::toupper(*b))) == 0 && *a)
302 std::set<std::string, InsensitiveCompare> orderedDirectories;
303 std::set<std::string, InsensitiveCompare> orderedFiles;
305 std::string extension;
308 while((entry = readdir(pDIR)))
310 name = std::string(entry->d_name);
311 type = int(entry->d_type);
317 type == 4 || type == 8))
325 opendir((CodeEditor::SOURCE_BASE_PATH + path +
"/" + name).c_str());
343 orderedDirectories.emplace(name);
352 safeExtensionString(name.substr(name.rfind(
'.')));
355 orderedFiles.emplace(name);
359 __COUT__ <<
"Invalid file extension, skipping '" << name <<
"' ..."
368 __COUT__ <<
"Found " << orderedDirectories.size() <<
" directories." << __E__;
369 __COUT__ <<
"Found " << orderedFiles.size() <<
" files." << __E__;
371 for(
const auto& name : orderedDirectories)
372 xmlOut->addTextElementToData(
"directory", name);
373 for(
const auto& name : orderedFiles)
374 xmlOut->addTextElementToData(
"file", name);
379 void CodeEditor::getFileContent(cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut)
381 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
382 path = safePathString(CgiDataUtilities::decodeURIComponent(path));
383 xmlOut->addTextElementToData(
"path", path);
385 std::string extension = CgiDataUtilities::getData(cgiIn,
"ext");
386 extension = safeExtensionString(extension);
387 xmlOut->addTextElementToData(
"ext", extension);
389 std::string contents;
391 if((i = path.find(
"$USER_DATA/")) == 0 ||
392 (i == 1 && path[0] ==
'/'))
393 CodeEditor::readFile(
394 CodeEditor::USER_DATA_PATH,
395 path.substr(i + std::string(
"$USER_DATA/").size()) +
"." + extension,
397 else if((i = path.find(
"$OTSDAQ_DATA/")) == 0 ||
398 (i == 1 && path[0] ==
'/'))
399 CodeEditor::readFile(
400 CodeEditor::OTSDAQ_DATA_PATH,
401 path.substr(std::string(
"/$OTSDAQ_DATA/").size()) +
"." + extension,
404 CodeEditor::readFile(
405 CodeEditor::SOURCE_BASE_PATH, path +
"." + extension, contents);
407 xmlOut->addTextElementToData(
"content", contents);
413 void CodeEditor::readFile(
const std::string& basepath,
414 const std::string& path,
415 std::string& contents)
417 std::string fullpath = basepath +
"/" + path;
420 std::FILE* fp = std::fopen(fullpath.c_str(),
"rb");
423 __SS__ <<
"Could not open file at " << path << __E__;
427 std::fseek(fp, 0, SEEK_END);
428 contents.resize(std::ftell(fp));
430 std::fread(&contents[0], 1, contents.size(), fp);
436 void CodeEditor::writeFile(
const std::string& basepath,
437 const std::string& path,
438 const std::string& contents,
439 const std::string& username,
440 const unsigned long long& insertPos,
441 const std::string& insertString)
443 std::string fullpath = basepath + path;
449 fp = fopen(fullpath.c_str(),
"rb");
452 __SS__ <<
"Could not open file for saving at " << fullpath << __E__;
455 std::fseek(fp, 0, SEEK_END);
456 long long int oldSize = std::ftell(fp);
459 fp = fopen(fullpath.c_str(),
"wb");
462 __SS__ <<
"Could not open file for saving at " << fullpath << __E__;
466 if(insertPos == (
unsigned long long)-1)
467 std::fwrite(&contents[0], 1, contents.size(), fp);
470 std::fwrite(&contents[0], 1, insertPos, fp);
471 std::fwrite(&insertString[0], 1, insertString.size(), fp);
472 std::fwrite(&contents[insertPos], 1, contents.size() - insertPos, fp);
478 std::string logpath = CODE_EDITOR_DATA_PATH +
"/codeEditorChangeLog.txt";
479 fp = fopen(logpath.c_str(),
"a");
482 __SS__ <<
"Could not open change log for change tracking at " << logpath
487 "time=%lld author%s old-size=%lld new-size=%lld path=%s\n",
491 (
long long)contents.size(),
495 __COUT__ <<
"Changes logged to: " << logpath << __E__;
502 void CodeEditor::saveFileContent(cgicc::Cgicc& cgiIn,
504 const std::string& username)
506 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
507 path = safePathString(CgiDataUtilities::decodeURIComponent(path));
508 xmlOut->addTextElementToData(
"path", path);
510 std::string extension = CgiDataUtilities::getData(cgiIn,
"ext");
511 extension = safeExtensionString(extension);
512 xmlOut->addTextElementToData(
"ext", extension);
514 std::string contents = CgiDataUtilities::postData(cgiIn,
"content");
516 contents = StringMacros::decodeURIComponent(contents);
518 CodeEditor::writeFile(
519 CodeEditor::SOURCE_BASE_PATH, path +
"." + extension, contents, username);
526 void CodeEditor::build(cgicc::Cgicc& cgiIn,
528 const std::string& username)
530 bool clean = CgiDataUtilities::getDataAsInt(cgiIn,
"clean") ?
true :
false;
532 __MCOUT_INFO__(
"Build (clean=" << clean <<
") launched by '" << username <<
"'..."
546 std::array<char, 128> buffer;
548 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
550 __THROW__(
"popen() failed!");
555 while(!feof(pipe.get()))
557 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
559 result += buffer.data();
562 i = result.find(
'\n');
563 __COUTV__(result.substr(0, i));
564 __MOUT__ << result.substr(0, i);
565 result = result.substr(i + 1);
570 __MOUT__ << result.substr(0, i);
576 cmd =
"source mrbSetEnv 2>&1";
578 std::array<char, 128> buffer;
580 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
582 __THROW__(
"popen() failed!");
587 while(!feof(pipe.get()))
589 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
591 result += buffer.data();
594 i = result.find(
'\n');
595 __COUTV__(result.substr(0, i));
596 __MOUT__ << result.substr(0, i);
597 result = result.substr(i + 1);
602 __MOUT__ << result.substr(0, i);
611 std::array<char, 128> buffer;
613 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
615 __THROW__(
"popen() failed!");
620 while(!feof(pipe.get()))
622 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
624 result += buffer.data();
627 i = result.find(
'\n');
629 __MOUT__ << result.substr(0, i);
630 result = result.substr(i + 1);
635 __MOUT__ << result.substr(0, i);
644 std::map<std::string , std::set<std::string> >
645 CodeEditor::getSpecialsMap(
void)
647 std::map<std::string , std::set<std::string> >
649 std::string path = std::string(getenv(
"MRB_SOURCE"));
653 const unsigned int numOfSpecials = 7;
654 std::string specialFolders[] = {
"FEInterfaces",
655 "DataProcessorPlugins",
656 "UserTableDataFormats",
657 "TablePluginDataFormats",
658 "SlowControlsInterfacePlugins",
659 "FEInterfacePlugins",
661 std::string specialMapTypes[] = {CodeEditor::SPECIAL_TYPE_FEInterface,
662 CodeEditor::SPECIAL_TYPE_DataProcessor,
663 CodeEditor::SPECIAL_TYPE_Table,
664 CodeEditor::SPECIAL_TYPE_Table,
665 CodeEditor::SPECIAL_TYPE_ControlsInterface,
666 CodeEditor::SPECIAL_TYPE_FEInterface,
667 CodeEditor::SPECIAL_TYPE_Tools};
673 const std::string&,
const std::string&,
const unsigned int,
const int)>
674 localRecurse = [&specialFolders, &specialMapTypes, &retMap, &localRecurse](
675 const std::string& path,
676 const std::string& offsetPath,
677 const unsigned int depth,
678 const int specialIndex) {
684 struct dirent* entry;
686 if(!(pDIR = opendir(path.c_str())))
688 __SS__ <<
"Plugin base path '" << path <<
"' could not be opened!"
696 int childSpecialIndex;
697 while((entry = readdir(pDIR)))
699 name = std::string(entry->d_name);
700 type = int(entry->d_type);
706 type == 4 || type == 8))
713 DIR* pTmpDIR = opendir((path +
"/" + name).c_str());
731 childSpecialIndex = -1;
732 for(
unsigned int i = 0; i < numOfSpecials; ++i)
733 if(name == specialFolders[i])
739 childSpecialIndex = i;
745 localRecurse(path +
"/" + name,
746 offsetPath +
"/" + name,
750 else if(specialIndex >= 0)
754 if(name.find(
".h") == name.length() - 2 ||
755 name.find(
".cc") == name.length() - 3 ||
756 name.find(
".txt") == name.length() - 4 ||
757 name.find(
".sh") == name.length() - 3 ||
758 name.find(
".py") == name.length() - 3)
765 retMap[specialMapTypes[specialIndex]].emplace(offsetPath +
777 localRecurse(path,
"" , 0 , -1 );