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(__ENV__("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(__ENV__(
"MRB_SOURCE")) +
"/";
27 const std::string CodeEditor::USER_DATA_PATH = std::string(__ENV__(
"USER_DATA")) +
"/";
28 const std::string CodeEditor::OTSDAQ_DATA_PATH =
29 std::string(__ENV__(
"OTSDAQ_DATA")) +
"/";
33 CodeEditor::CodeEditor()
34 : ALLOWED_FILE_EXTENSIONS_({
"h",
54 std::string path = CODE_EDITOR_DATA_PATH;
55 DIR* dir = opendir(path.c_str());
58 else if(-1 == mkdir(path.c_str(), 0755))
61 __SS__ <<
"Service directory creation failed: " << path << std::endl;
70 void CodeEditor::xmlRequest(
const std::string& option,
73 const std::string& username)
try
87 if(option ==
"getDirectoryContent")
89 getDirectoryContent(cgiIn, xmlOut);
91 else if(option ==
"getFileContent")
93 getFileContent(cgiIn, xmlOut);
95 else if(option ==
"saveFileContent")
97 saveFileContent(cgiIn, xmlOut, username);
99 else if(option ==
"build")
101 build(cgiIn, xmlOut, username);
103 else if(option ==
"getAllowedExtensions")
105 xmlOut->addTextElementToData(
107 StringMacros::setToString(ALLOWED_FILE_EXTENSIONS_,
","));
111 __SS__ <<
"Unrecognized request option '" << option <<
".'" << __E__;
115 catch(
const std::runtime_error& e)
117 __SS__ <<
"Error encountered while handling the Code Editor request option '"
118 << option <<
"': " << e.what() << __E__;
119 xmlOut->addTextElementToData(
"Error", ss.str());
123 __SS__ <<
"Unknown error encountered while handling the Code Editor request option '"
124 << option <<
"!'" << __E__;
125 xmlOut->addTextElementToData(
"Error", ss.str());
130 std::string CodeEditor::safePathString(
const std::string& path)
134 std::string fullpath =
"";
135 for(
unsigned int i = 0; i < path.length(); ++i)
136 if((path[i] >=
'a' && path[i] <=
'z') || (path[i] >=
'A' && path[i] <=
'Z') ||
137 path[i] >=
'_' || path[i] >=
'-' || path[i] >=
' ' || path[i] >=
'/')
140 if(!fullpath.length())
142 __SS__ <<
"Invalid path '" << fullpath <<
"' found!" << __E__;
151 std::string CodeEditor::safeExtensionString(
const std::string& extension)
155 std::string retExt =
"";
159 for(
unsigned int i = 0; i < extension.length(); ++i)
160 if((extension[i] >=
'a' && extension[i] <=
'z'))
161 retExt += extension[i];
162 else if((extension[i] >=
'A' && extension[i] <=
'Z'))
163 retExt += extension[i] + 32;
164 else if(i > 0 || extension[i] !=
'.')
166 __SS__ <<
"Invalid extension non-alpha " << int(extension[i]) <<
" found!"
173 if(ALLOWED_FILE_EXTENSIONS_.find(
175 ALLOWED_FILE_EXTENSIONS_.end())
177 __SS__ <<
"Invalid extension '" << retExt <<
"' found!" << __E__;
185 void CodeEditor::getDirectoryContent(cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut)
187 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
188 path = safePathString(CgiDataUtilities::decodeURIComponent(path));
190 __COUTV__(CodeEditor::SOURCE_BASE_PATH);
192 xmlOut->addTextElementToData(
"path", path);
194 const unsigned int numOfTypes = 7;
195 std::string specialTypeNames[] = {
"Front-End Plugins",
196 "Data Processor Plugins",
197 "Configuration Table Plugins",
198 "Controls Interface Plugins",
202 std::string specialTypes[] = {SPECIAL_TYPE_FEInterface,
203 SPECIAL_TYPE_DataProcessor,
205 SPECIAL_TYPE_ControlsInterface,
207 SPECIAL_TYPE_UserData,
208 SPECIAL_TYPE_OutputData};
210 std::string pathMatchPrepend =
"/";
212 if(path.length() > 1 && path[1] ==
'/')
213 pathMatchPrepend +=
'/';
215 for(
unsigned int i = 0; i < numOfTypes; ++i)
216 if(path == pathMatchPrepend + specialTypeNames[i])
218 __COUT__ <<
"Getting all " << specialTypeNames[i] <<
"..." << __E__;
222 if(specialTypes[i] == SPECIAL_TYPE_UserData)
224 getPathContent(
"/", CodeEditor::USER_DATA_PATH, xmlOut);
227 else if(specialTypes[i] == SPECIAL_TYPE_OutputData)
229 getPathContent(
"/", CodeEditor::OTSDAQ_DATA_PATH, xmlOut);
233 std::map<std::string ,
234 std::set<std::string> >
235 retMap = CodeEditor::getSpecialsMap();
236 if(retMap.find(specialTypes[i]) != retMap.end())
238 for(
const auto& specialTypeFile : retMap[specialTypes[i]])
240 xmlOut->addTextElementToData(
"specialFile", specialTypeFile);
245 __SS__ <<
"No files for type '" << specialTypeNames[i] <<
"' were found."
254 for(
unsigned int i = 0; i < numOfTypes; ++i)
255 xmlOut->addTextElementToData(
"special", specialTypeNames[i]);
257 std::string contents;
259 if((i = path.find(
"$USER_DATA/")) == 0 ||
260 (i == 1 && path[0] ==
'/'))
261 getPathContent(CodeEditor::USER_DATA_PATH,
262 path.substr(std::string(
"/$USER_DATA/").size()),
264 else if((i = path.find(
"$OTSDAQ_DATA/")) == 0 ||
265 (i == 1 && path[0] ==
'/'))
266 getPathContent(CodeEditor::OTSDAQ_DATA_PATH,
267 path.substr(std::string(
"/$OTSDAQ_DATA/").size()),
270 getPathContent(CodeEditor::SOURCE_BASE_PATH, path, xmlOut);
276 void CodeEditor::getPathContent(
const std::string& basepath,
277 const std::string& path,
281 struct dirent* entry;
286 if(!(pDIR = opendir((basepath + path).c_str())))
288 __SS__ <<
"Path '" << path <<
"' could not be opened!" << __E__;
294 struct InsensitiveCompare
296 bool operator()(
const std::string& as,
const std::string& bs)
const
299 const char* a = as.c_str();
300 const char* b = bs.c_str();
304 while((d = (std::toupper(*a) - std::toupper(*b))) == 0 && *a)
312 std::set<std::string, InsensitiveCompare> orderedDirectories;
313 std::set<std::string, InsensitiveCompare> orderedFiles;
315 std::string extension;
318 while((entry = readdir(pDIR)))
320 name = std::string(entry->d_name);
321 type = int(entry->d_type);
327 type == 4 || type == 8))
335 opendir((CodeEditor::SOURCE_BASE_PATH + path +
"/" + name).c_str());
353 orderedDirectories.emplace(name);
363 safeExtensionString(name.substr(name.rfind(
'.')));
366 orderedFiles.emplace(name);
370 __COUT__ <<
"Invalid file extension, skipping '" << name <<
"' ..."
379 __COUT__ <<
"Found " << orderedDirectories.size() <<
" directories." << __E__;
380 __COUT__ <<
"Found " << orderedFiles.size() <<
" files." << __E__;
382 for(
const auto& name : orderedDirectories)
383 xmlOut->addTextElementToData(
"directory", name);
384 for(
const auto& name : orderedFiles)
385 xmlOut->addTextElementToData(
"file", name);
390 void CodeEditor::getFileContent(cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut)
392 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
393 path = safePathString(CgiDataUtilities::decodeURIComponent(path));
394 xmlOut->addTextElementToData(
"path", path);
396 std::string extension = CgiDataUtilities::getData(cgiIn,
"ext");
397 if(!(path.length() > 4 && path.substr(path.length() - 4) ==
"/ots"))
398 extension = safeExtensionString(extension);
399 xmlOut->addTextElementToData(
"ext", extension);
401 std::string contents;
403 if((i = path.find(
"$USER_DATA/")) == 0 ||
404 (i == 1 && path[0] ==
'/'))
405 CodeEditor::readFile(CodeEditor::USER_DATA_PATH,
406 path.substr(i + std::string(
"$USER_DATA/").size()) +
407 (extension.size() ?
"." :
"") + extension,
409 else if((i = path.find(
"$OTSDAQ_DATA/")) == 0 ||
410 (i == 1 && path[0] ==
'/'))
411 CodeEditor::readFile(CodeEditor::OTSDAQ_DATA_PATH,
412 path.substr(std::string(
"/$OTSDAQ_DATA/").size()) +
413 (extension.size() ?
"." :
"") + extension,
416 CodeEditor::readFile(CodeEditor::SOURCE_BASE_PATH,
417 path + (extension.size() ?
"." :
"") + extension,
420 xmlOut->addTextElementToData(
"content", contents);
426 void CodeEditor::readFile(
const std::string& basepath,
427 const std::string& path,
428 std::string& contents)
430 std::string fullpath = basepath +
"/" + path;
433 std::FILE* fp = std::fopen(fullpath.c_str(),
"rb");
436 __SS__ <<
"Could not open file at " << path << __E__;
440 std::fseek(fp, 0, SEEK_END);
441 contents.resize(std::ftell(fp));
443 std::fread(&contents[0], 1, contents.size(), fp);
449 void CodeEditor::writeFile(
const std::string& basepath,
450 const std::string& path,
451 const std::string& contents,
452 const std::string& username,
453 const unsigned long long& insertPos,
454 const std::string& insertString)
456 std::string fullpath = basepath + path;
461 long long int oldSize = 0;
465 fp = fopen(fullpath.c_str(),
"rb");
468 __SS__ <<
"Could not open file for saving at " << fullpath << __E__;
471 std::fseek(fp, 0, SEEK_END);
472 oldSize = std::ftell(fp);
477 __COUT_WARN__ <<
"Ignoring file not existing..." << __E__;
480 fp = fopen(fullpath.c_str(),
"wb");
483 __SS__ <<
"Could not open file for saving at " << fullpath << __E__;
487 if(insertPos == (
unsigned long long)-1)
488 std::fwrite(&contents[0], 1, contents.size(), fp);
491 std::fwrite(&contents[0], 1, insertPos, fp);
492 std::fwrite(&insertString[0], 1, insertString.size(), fp);
493 std::fwrite(&contents[insertPos], 1, contents.size() - insertPos, fp);
499 std::string logpath = CODE_EDITOR_DATA_PATH +
"/codeEditorChangeLog.txt";
500 fp = fopen(logpath.c_str(),
"a");
503 __SS__ <<
"Could not open change log for change tracking at " << logpath
508 "time=%lld author%s old-size=%lld new-size=%lld path=%s\n",
512 (
long long)contents.size(),
516 __COUT__ <<
"Changes logged to: " << logpath << __E__;
523 void CodeEditor::saveFileContent(cgicc::Cgicc& cgiIn,
525 const std::string& username)
527 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
528 path = safePathString(CgiDataUtilities::decodeURIComponent(path));
529 xmlOut->addTextElementToData(
"path", path);
531 std::string basepath = CodeEditor::SOURCE_BASE_PATH;
533 std::string pathMatchPrepend =
"/";
535 if(path.length() > 1 && path[1] ==
'/')
536 pathMatchPrepend +=
'/';
539 __COUTV__(pathMatchPrepend);
542 if(path.substr(0, (pathMatchPrepend +
"$USER_DATA/").size()) ==
543 pathMatchPrepend +
"$USER_DATA/")
546 path = CodeEditor::USER_DATA_PATH +
"/" +
547 path.substr((pathMatchPrepend +
"$USER_DATA/").size());
549 else if(path.substr(0, (pathMatchPrepend +
"$OTSDAQ_DATA/").size()) ==
550 pathMatchPrepend +
"$OTSDAQ_DATA/")
553 path = CodeEditor::OTSDAQ_DATA_PATH +
"/" +
554 path.substr((pathMatchPrepend +
"$OTSDAQ_DATA/").size());
559 std::string extension = CgiDataUtilities::getData(cgiIn,
"ext");
560 if(!(path.length() > 4 && path.substr(path.length() - 4) ==
"/ots"))
561 extension = safeExtensionString(extension);
562 xmlOut->addTextElementToData(
"ext", extension);
564 std::string contents = CgiDataUtilities::postData(cgiIn,
"content");
566 contents = StringMacros::decodeURIComponent(contents);
568 CodeEditor::writeFile(
569 basepath, path + (extension.size() ?
"." :
"") + extension, contents, username);
576 void CodeEditor::build(cgicc::Cgicc& cgiIn,
578 const std::string& username)
580 bool clean = CgiDataUtilities::getDataAsInt(cgiIn,
"clean") ?
true :
false;
582 __MCOUT_INFO__(
"Build (clean=" << clean <<
") launched by '" << username <<
"'..."
596 std::array<char, 128> buffer;
598 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
600 __THROW__(
"popen() failed!");
605 while(!feof(pipe.get()))
607 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
609 result += buffer.data();
612 i = result.find(
'\n');
613 __COUTV__(result.substr(0, i));
614 __MOUT__ << result.substr(0, i);
615 result = result.substr(i + 1);
620 __MOUT__ << result.substr(0, i);
626 cmd =
"source mrbSetEnv 2>&1";
628 std::array<char, 128> buffer;
630 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
632 __THROW__(
"popen() failed!");
637 while(!feof(pipe.get()))
639 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
641 result += buffer.data();
644 i = result.find(
'\n');
645 __COUTV__(result.substr(0, i));
646 __MOUT__ << result.substr(0, i);
647 result = result.substr(i + 1);
652 __MOUT__ << result.substr(0, i);
661 std::array<char, 128> buffer;
663 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
665 __THROW__(
"popen() failed!");
670 while(!feof(pipe.get()))
672 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
674 result += buffer.data();
677 i = result.find(
'\n');
679 __MOUT__ << result.substr(0, i);
680 result = result.substr(i + 1);
685 __MOUT__ << result.substr(0, i);
694 std::map<std::string , std::set<std::string> >
695 CodeEditor::getSpecialsMap(
void)
697 std::map<std::string , std::set<std::string> >
699 std::string path = std::string(__ENV__(
"MRB_SOURCE"));
703 const unsigned int numOfSpecials = 7;
704 std::string specialFolders[] = {
"FEInterfaces",
705 "DataProcessorPlugins",
706 "UserTableDataFormats",
710 "UserTablePluginDataFormats",
711 "SlowControlsInterfacePlugins",
712 "ControlsInterfacePlugins",
713 "FEInterfacePlugins",
715 std::string specialMapTypes[] = {CodeEditor::SPECIAL_TYPE_FEInterface,
716 CodeEditor::SPECIAL_TYPE_DataProcessor,
717 CodeEditor::SPECIAL_TYPE_Table,
718 CodeEditor::SPECIAL_TYPE_Table,
719 CodeEditor::SPECIAL_TYPE_Table,
720 CodeEditor::SPECIAL_TYPE_Table,
721 CodeEditor::SPECIAL_TYPE_Table,
722 CodeEditor::SPECIAL_TYPE_ControlsInterface,
723 CodeEditor::SPECIAL_TYPE_ControlsInterface,
724 CodeEditor::SPECIAL_TYPE_FEInterface,
725 CodeEditor::SPECIAL_TYPE_Tools};
731 const std::string&,
const std::string&,
const unsigned int,
const int)>
732 localRecurse = [&specialFolders, &specialMapTypes, &retMap, &localRecurse](
733 const std::string& path,
734 const std::string& offsetPath,
735 const unsigned int depth,
736 const int specialIndex) {
742 struct dirent* entry;
744 if(!(pDIR = opendir(path.c_str())))
746 __SS__ <<
"Plugin base path '" << path <<
"' could not be opened!"
754 int childSpecialIndex;
755 while((entry = readdir(pDIR)))
757 name = std::string(entry->d_name);
758 type = int(entry->d_type);
764 type == 4 || type == 8))
771 DIR* pTmpDIR = opendir((path +
"/" + name).c_str());
789 childSpecialIndex = -1;
790 for(
unsigned int i = 0; i < numOfSpecials; ++i)
791 if(name == specialFolders[i])
797 childSpecialIndex = i;
803 localRecurse(path +
"/" + name,
804 offsetPath +
"/" + name,
808 else if(specialIndex >= 0)
812 if(name.find(
".h") == name.length() - 2 ||
813 name.find(
".cc") == name.length() - 3 ||
814 name.find(
".txt") == name.length() - 4 ||
815 name.find(
".sh") == name.length() - 3 ||
816 name.find(
".py") == name.length() - 3)
823 retMap[specialMapTypes[specialIndex]].emplace(offsetPath +
835 localRecurse(path,
"" , 0 , -1 );