1 #include "otsdaq/CodeEditor/CodeEditor.h"
2 #include "otsdaq/CgiDataUtilities/CgiDataUtilities.h"
3 #include "otsdaq/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",
"hh",
"hpp",
"hxx",
"c",
"cc",
"cpp",
35 "cxx",
"icc",
"dat",
"txt",
"sh",
"css",
"html",
36 "htm",
"js",
"py",
"fcl",
"xml",
"cfg"})
38 std::string path = CODE_EDITOR_DATA_PATH;
39 DIR* dir = opendir(path.c_str());
42 else if(-1 == mkdir(path.c_str(), 0755))
45 __SS__ <<
"Service directory creation failed: " << path << std::endl;
54 void CodeEditor::xmlRequest(
const std::string& option,
58 const std::string& username)
try
72 if(option ==
"getDirectoryContent")
74 getDirectoryContent(cgiIn, xmlOut);
76 else if(option ==
"getFileContent")
78 getFileContent(cgiIn, xmlOut);
80 else if(!readOnlyMode && option ==
"saveFileContent")
82 saveFileContent(cgiIn, xmlOut, username);
84 else if(!readOnlyMode && option ==
"build")
86 build(cgiIn, xmlOut, username);
88 else if(option ==
"getAllowedExtensions")
90 xmlOut->addTextElementToData(
92 StringMacros::setToString(ALLOWED_FILE_EXTENSIONS_,
","));
96 __SS__ <<
"Unrecognized request option '" << option <<
".'" << __E__;
100 catch(
const std::runtime_error& e)
102 __SS__ <<
"Error encountered while handling the Code Editor request option '"
103 << option <<
"': " << e.what() << __E__;
104 xmlOut->addTextElementToData(
"Error", ss.str());
108 __SS__ <<
"Unknown error encountered while handling the Code Editor request option '"
109 << option <<
"!'" << __E__;
110 xmlOut->addTextElementToData(
"Error", ss.str());
115 std::string CodeEditor::safePathString(
const std::string& path)
119 std::string fullpath =
"";
120 for(
unsigned int i = 0; i < path.length(); ++i)
121 if((path[i] >=
'a' && path[i] <=
'z') || (path[i] >=
'A' && path[i] <=
'Z') ||
122 path[i] >=
'_' || path[i] >=
'-' || path[i] >=
' ' || path[i] >=
'/')
125 if(!fullpath.length())
127 __SS__ <<
"Invalid path '" << fullpath <<
"' found!" << __E__;
136 std::string CodeEditor::safeExtensionString(
const std::string& extension)
140 std::string retExt =
"";
144 for(
unsigned int i = 0; i < extension.length(); ++i)
145 if((extension[i] >=
'a' && extension[i] <=
'z'))
146 retExt += extension[i];
147 else if((extension[i] >=
'A' && extension[i] <=
'Z'))
148 retExt += extension[i] + 32;
149 else if(i > 0 || extension[i] !=
'.')
151 __SS__ <<
"Invalid extension non-alpha " << int(extension[i]) <<
" found!"
158 if(ALLOWED_FILE_EXTENSIONS_.find(
160 ALLOWED_FILE_EXTENSIONS_.end())
162 __SS__ <<
"Invalid extension '" << retExt <<
"' found!" << __E__;
170 void CodeEditor::getDirectoryContent(cgicc::Cgicc& cgiIn,
HttpXmlDocument* xmlOut)
172 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
173 path = safePathString(StringMacros::decodeURIComponent(path));
175 __COUTV__(CodeEditor::SOURCE_BASE_PATH);
177 xmlOut->addTextElementToData(
"path", path);
179 const unsigned int numOfTypes = 7;
180 std::string specialTypeNames[] = {
"Front-End Plugins",
181 "Data Processor Plugins",
182 "Configuration Table Plugins",
183 "Controls Interface Plugins",
187 std::string specialTypes[] = {SPECIAL_TYPE_FEInterface,
188 SPECIAL_TYPE_DataProcessor,
190 SPECIAL_TYPE_ControlsInterface,
192 SPECIAL_TYPE_UserData,
193 SPECIAL_TYPE_OutputData};
195 std::string pathMatchPrepend =
"/";
197 if(path.length() > 1 && path[1] ==
'/')
198 pathMatchPrepend +=
'/';
200 for(
unsigned int i = 0; i < numOfTypes; ++i)
201 if(path == pathMatchPrepend + specialTypeNames[i])
203 __COUT__ <<
"Getting all " << specialTypeNames[i] <<
"..." << __E__;
207 if(specialTypes[i] == SPECIAL_TYPE_UserData)
209 getPathContent(
"/", CodeEditor::USER_DATA_PATH, xmlOut);
212 else if(specialTypes[i] == SPECIAL_TYPE_OutputData)
214 getPathContent(
"/", CodeEditor::OTSDAQ_DATA_PATH, xmlOut);
218 std::map<std::string ,
219 std::set<std::string> >
220 retMap = CodeEditor::getSpecialsMap();
221 if(retMap.find(specialTypes[i]) != retMap.end())
223 for(
const auto& specialTypeFile : retMap[specialTypes[i]])
225 xmlOut->addTextElementToData(
"specialFile", specialTypeFile);
230 __SS__ <<
"No files for type '" << specialTypeNames[i] <<
"' were found."
239 for(
unsigned int i = 0; i < numOfTypes; ++i)
240 xmlOut->addTextElementToData(
"special", specialTypeNames[i]);
242 std::string contents;
244 if((i = path.find(
"$USER_DATA/")) == 0 ||
245 (i == 1 && path[0] ==
'/'))
246 getPathContent(CodeEditor::USER_DATA_PATH,
247 path.substr(std::string(
"/$USER_DATA/").size()),
249 else if((i = path.find(
"$OTSDAQ_DATA/")) == 0 ||
250 (i == 1 && path[0] ==
'/'))
251 getPathContent(CodeEditor::OTSDAQ_DATA_PATH,
252 path.substr(std::string(
"/$OTSDAQ_DATA/").size()),
255 getPathContent(CodeEditor::SOURCE_BASE_PATH, path, xmlOut);
261 void CodeEditor::getPathContent(
const std::string& basepath,
262 const std::string& path,
266 struct dirent* entry;
271 if(!(pDIR = opendir((basepath + path).c_str())))
273 __SS__ <<
"Path '" << path <<
"' could not be opened!" << __E__;
279 struct InsensitiveCompare
281 bool operator()(
const std::string& as,
const std::string& bs)
const
284 const char* a = as.c_str();
285 const char* b = bs.c_str();
289 while((d = (std::toupper(*a) - std::toupper(*b))) == 0 && *a)
297 std::set<std::string, InsensitiveCompare> orderedDirectories;
298 std::set<std::string, InsensitiveCompare> orderedFiles;
300 std::string extension;
303 while((entry = readdir(pDIR)))
305 name = std::string(entry->d_name);
306 type = int(entry->d_type);
319 if(type == 0 || type == 10)
322 DIR* pTmpDIR = opendir((basepath + path +
"/" + name).c_str());
342 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(StringMacros::decodeURIComponent(path));
383 xmlOut->addTextElementToData(
"path", path);
385 std::string extension = CgiDataUtilities::getData(cgiIn,
"ext");
386 if(extension ==
"ots")
389 if(!(path.length() > 4 && path.substr(path.length() - 4) ==
"/ots"))
390 extension = safeExtensionString(extension);
391 xmlOut->addTextElementToData(
"ext", extension);
393 std::string contents;
395 if((i = path.find(
"$USER_DATA/")) == 0 ||
396 (i == 1 && path[0] ==
'/'))
397 CodeEditor::readFile(CodeEditor::USER_DATA_PATH,
398 path.substr(i + std::string(
"$USER_DATA/").size()) +
399 (extension.size() ?
"." :
"") + extension,
401 else if((i = path.find(
"$OTSDAQ_DATA/")) == 0 ||
402 (i == 1 && path[0] ==
'/'))
403 CodeEditor::readFile(CodeEditor::OTSDAQ_DATA_PATH,
404 path.substr(std::string(
"/$OTSDAQ_DATA/").size()) +
405 (extension.size() ?
"." :
"") + extension,
408 CodeEditor::readFile(CodeEditor::SOURCE_BASE_PATH,
409 path + (extension.size() ?
"." :
"") + extension,
412 xmlOut->addTextElementToData(
"content", contents);
418 void CodeEditor::readFile(
const std::string& basepath,
419 const std::string& path,
420 std::string& contents)
422 std::string fullpath = basepath +
"/" + path;
425 std::FILE* fp = std::fopen(fullpath.c_str(),
"rb");
428 __SS__ <<
"Could not open file at " << path << __E__;
432 std::fseek(fp, 0, SEEK_END);
433 contents.resize(std::ftell(fp));
435 std::fread(&contents[0], 1, contents.size(), fp);
441 void CodeEditor::writeFile(
const std::string& basepath,
442 const std::string& path,
443 const std::string& contents,
444 const std::string& username,
445 const unsigned long long& insertPos,
446 const std::string& insertString)
448 std::string fullpath = basepath + path;
453 long long int oldSize = 0;
457 fp = fopen(fullpath.c_str(),
"rb");
460 __SS__ <<
"Could not open file for saving at " << fullpath << __E__;
463 std::fseek(fp, 0, SEEK_END);
464 oldSize = std::ftell(fp);
469 __COUT_WARN__ <<
"Ignoring file not existing..." << __E__;
472 fp = fopen(fullpath.c_str(),
"wb");
475 __SS__ <<
"Could not open file for saving at " << fullpath << __E__;
479 if(insertPos == (
unsigned long long)-1)
480 std::fwrite(&contents[0], 1, contents.size(), fp);
483 std::fwrite(&contents[0], 1, insertPos, fp);
484 std::fwrite(&insertString[0], 1, insertString.size(), fp);
485 std::fwrite(&contents[insertPos], 1, contents.size() - insertPos, fp);
491 std::string logpath = CODE_EDITOR_DATA_PATH +
"/codeEditorChangeLog.txt";
492 fp = fopen(logpath.c_str(),
"a");
495 __SS__ <<
"Could not open change log for change tracking at " << logpath
500 "time=%lld author%s old-size=%lld new-size=%lld path=%s\n",
504 (
long long)contents.size(),
508 __COUT__ <<
"Changes logged to: " << logpath << __E__;
515 void CodeEditor::saveFileContent(cgicc::Cgicc& cgiIn,
517 const std::string& username)
519 std::string path = CgiDataUtilities::getData(cgiIn,
"path");
520 path = safePathString(StringMacros::decodeURIComponent(path));
521 xmlOut->addTextElementToData(
"path", path);
523 std::string basepath = CodeEditor::SOURCE_BASE_PATH;
525 std::string pathMatchPrepend =
"/";
527 if(path.length() > 1 && path[1] ==
'/')
528 pathMatchPrepend +=
'/';
531 __COUTV__(pathMatchPrepend);
534 if(path.substr(0, (pathMatchPrepend +
"$USER_DATA/").size()) ==
535 pathMatchPrepend +
"$USER_DATA/")
538 path = CodeEditor::USER_DATA_PATH +
"/" +
539 path.substr((pathMatchPrepend +
"$USER_DATA/").size());
541 else if(path.substr(0, (pathMatchPrepend +
"$OTSDAQ_DATA/").size()) ==
542 pathMatchPrepend +
"$OTSDAQ_DATA/")
545 path = CodeEditor::OTSDAQ_DATA_PATH +
"/" +
546 path.substr((pathMatchPrepend +
"$OTSDAQ_DATA/").size());
551 std::string extension = CgiDataUtilities::getData(cgiIn,
"ext");
552 if(!(path.length() > 4 && path.substr(path.length() - 4) ==
"/ots"))
553 extension = safeExtensionString(extension);
554 xmlOut->addTextElementToData(
"ext", extension);
556 std::string contents = CgiDataUtilities::postData(cgiIn,
"content");
558 contents = StringMacros::decodeURIComponent(contents);
560 CodeEditor::writeFile(
561 basepath, path + (extension.size() ?
"." :
"") + extension, contents, username);
568 void CodeEditor::build(cgicc::Cgicc& cgiIn,
570 const std::string& username)
572 bool clean = CgiDataUtilities::getDataAsInt(cgiIn,
"clean") ?
true :
false;
574 __MCOUT_INFO__(
"Build (clean=" << clean <<
") launched by '" << username <<
"'..."
588 std::array<char, 128> buffer;
590 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
592 __THROW__(
"popen() failed!");
597 while(!feof(pipe.get()))
599 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
601 result += buffer.data();
604 i = result.find(
'\n');
605 __COUTV__(result.substr(0, i));
606 __MOUT__ << result.substr(0, i);
607 result = result.substr(i + 1);
612 __MOUT__ << result.substr(0, i);
618 cmd =
"source mrbSetEnv 2>&1";
620 std::array<char, 128> buffer;
622 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
624 __THROW__(
"popen() failed!");
629 while(!feof(pipe.get()))
631 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
633 result += buffer.data();
636 i = result.find(
'\n');
637 __COUTV__(result.substr(0, i));
638 __MOUT__ << result.substr(0, i);
639 result = result.substr(i + 1);
644 __MOUT__ << result.substr(0, i);
653 std::array<char, 128> buffer;
655 std::shared_ptr<FILE> pipe(popen(cmd.c_str(),
"r"), pclose);
657 __THROW__(
"popen() failed!");
662 while(!feof(pipe.get()))
664 if(fgets(buffer.data(), 128, pipe.get()) !=
nullptr)
666 result += buffer.data();
669 i = result.find(
'\n');
671 __MOUT__ << result.substr(0, i);
672 result = result.substr(i + 1);
677 __MOUT__ << result.substr(0, i);
686 std::map<std::string , std::set<std::string> >
687 CodeEditor::getSpecialsMap(
void)
689 std::map<std::string , std::set<std::string> >
691 std::string path = std::string(__ENV__(
"MRB_SOURCE"));
695 const unsigned int numOfSpecials = 7;
696 std::string specialFolders[] = {
"FEInterfaces",
697 "DataProcessorPlugins",
698 "UserTableDataFormats",
700 "TablePluginDataFormats",
702 "UserTablePluginDataFormats",
703 "SlowControlsInterfacePlugins",
704 "ControlsInterfacePlugins",
705 "FEInterfacePlugins",
707 std::string specialMapTypes[] = {CodeEditor::SPECIAL_TYPE_FEInterface,
708 CodeEditor::SPECIAL_TYPE_DataProcessor,
709 CodeEditor::SPECIAL_TYPE_Table,
710 CodeEditor::SPECIAL_TYPE_Table,
711 CodeEditor::SPECIAL_TYPE_Table,
712 CodeEditor::SPECIAL_TYPE_Table,
713 CodeEditor::SPECIAL_TYPE_Table,
714 CodeEditor::SPECIAL_TYPE_ControlsInterface,
715 CodeEditor::SPECIAL_TYPE_ControlsInterface,
716 CodeEditor::SPECIAL_TYPE_FEInterface,
717 CodeEditor::SPECIAL_TYPE_Tools};
723 const std::string&,
const std::string&,
const unsigned int,
const int)>
724 localRecurse = [&specialFolders, &specialMapTypes, &retMap, &localRecurse](
725 const std::string& path,
726 const std::string& offsetPath,
727 const unsigned int depth,
728 const int specialIndex) {
734 struct dirent* entry;
736 if(!(pDIR = opendir(path.c_str())))
738 __SS__ <<
"Plugin base path '" << path <<
"' could not be opened!"
746 int childSpecialIndex;
747 while((entry = readdir(pDIR)))
749 name = std::string(entry->d_name);
750 type = int(entry->d_type);
756 type == 4 || type == 8))
763 DIR* pTmpDIR = opendir((path +
"/" + name).c_str());
781 childSpecialIndex = -1;
782 for(
unsigned int i = 0; i < numOfSpecials; ++i)
783 if(name == specialFolders[i])
789 childSpecialIndex = i;
795 localRecurse(path +
"/" + name,
796 offsetPath +
"/" + name,
800 else if(specialIndex >= 0)
804 if(name.find(
".h") == name.length() - 2 ||
805 name.find(
".cc") == name.length() - 3 ||
806 name.find(
".txt") == name.length() - 4 ||
807 name.find(
".sh") == name.length() - 3 ||
808 name.find(
".py") == name.length() - 3)
815 retMap[specialMapTypes[specialIndex]].emplace(offsetPath +
827 localRecurse(path,
"" , 0 , -1 );